diff --git a/src/bosh_azure_cpi/lib/cloud/azure/models/config.rb b/src/bosh_azure_cpi/lib/cloud/azure/models/config.rb index 09666f544..955125b9a 100644 --- a/src/bosh_azure_cpi/lib/cloud/azure/models/config.rb +++ b/src/bosh_azure_cpi/lib/cloud/azure/models/config.rb @@ -2,14 +2,30 @@ module Bosh::AzureCloud class LoadBalancerConfig - attr_reader :name, :resource_group_name - def initialize(resource_group_name, name) + attr_reader :name, :resource_group_name, :backend_pool_name + + def initialize(resource_group_name, name, backend_pool_name = nil) + @resource_group_name = resource_group_name + @name = name + @backend_pool_name = backend_pool_name + end + + def to_s + "name: #{@name}, resource_group_name: #{@resource_group_name}, backend_pool_name: #{@backend_pool_name}" + end + end + + class ApplicationGatewayConfig + attr_reader :name, :resource_group_name, :backend_pool_name + + def initialize(resource_group_name, name, backend_pool_name = nil) @resource_group_name = resource_group_name @name = name + @backend_pool_name = backend_pool_name end def to_s - "name: #{@name}, resource_group_name: #{@resource_group_name}" + "name: #{@name}, resource_group_name: #{@resource_group_name}, backend_pool_name: #{@backend_pool_name}" end end diff --git a/src/bosh_azure_cpi/lib/cloud/azure/models/vm_cloud_props.rb b/src/bosh_azure_cpi/lib/cloud/azure/models/vm_cloud_props.rb index dceb6d438..b6708c1af 100644 --- a/src/bosh_azure_cpi/lib/cloud/azure/models/vm_cloud_props.rb +++ b/src/bosh_azure_cpi/lib/cloud/azure/models/vm_cloud_props.rb @@ -9,8 +9,8 @@ class VMCloudProps attr_reader :root_disk, :ephemeral_disk, :caching attr_reader :availability_zone attr_reader :availability_set - attr_reader :load_balancer - attr_reader :application_gateway + attr_reader :load_balancers + attr_reader :application_gateways attr_reader :managed_identity attr_reader :security_group attr_reader :application_security_groups @@ -26,6 +26,8 @@ class VMCloudProps AVAILABILITY_SET_KEY = 'availability_set' LOAD_BALANCER_KEY = 'load_balancer' + APPLICATION_GATEWAY_KEY = 'application_gateway' + BACKEND_POOL_NAME_KEY = 'backend_pool_name' RESOURCE_GROUP_NAME_KEY = 'resource_group_name' NAME_KEY = 'name' @@ -56,8 +58,9 @@ def initialize(vm_properties, global_azure_config) @availability_set = _parse_availability_set_config(vm_properties, global_azure_config) cloud_error("Only one of 'availability_zone' and 'availability_set' is allowed to be configured for the VM but you have configured both.") if !@availability_zone.nil? && !@availability_set.name.nil? - @load_balancer = _parse_load_balancer_config(vm_properties, global_azure_config) - @application_gateway = vm_properties['application_gateway'] + @load_balancers = _parse_load_balancer_config(vm_properties, global_azure_config) + + @application_gateways = _parse_application_gateway_config(vm_properties) @managed_identity = global_azure_config.default_managed_identity managed_identity_hash = vm_properties.fetch('managed_identity', nil) @@ -99,21 +102,68 @@ def _default_fault_domain_count(global_azure_config) end end + # @return [Array,nil] def _parse_load_balancer_config(vm_properties, global_azure_config) - if vm_properties[LOAD_BALANCER_KEY].is_a?(Hash) - resource_group_name = vm_properties[LOAD_BALANCER_KEY][RESOURCE_GROUP_NAME_KEY] || global_azure_config.resource_group_name - Bosh::AzureCloud::LoadBalancerConfig.new( - resource_group_name, - vm_properties[LOAD_BALANCER_KEY][NAME_KEY] - ) - else - Bosh::AzureCloud::LoadBalancerConfig.new( - global_azure_config.resource_group_name, - vm_properties[LOAD_BALANCER_KEY] - ) + load_balancer_config = vm_properties[LOAD_BALANCER_KEY] + + return nil unless load_balancer_config + + cloud_error("Property '#{LOAD_BALANCER_KEY}' must be a String, Hash, or Array.") unless load_balancer_config.is_a?(String) || load_balancer_config.is_a?(Hash) || load_balancer_config.is_a?(Array) + + load_balancer_configs = load_balancer_config.is_a?(Array) ? load_balancer_config : [load_balancer_config] + load_balancers = Array(load_balancer_configs).flat_map do |lbc| + if lbc.is_a?(Hash) + load_balancer_names = lbc[NAME_KEY] + resource_group_name = lbc[RESOURCE_GROUP_NAME_KEY] + backend_pool_name = lbc[BACKEND_POOL_NAME_KEY] + else + load_balancer_names = lbc + resource_group_name = nil + backend_pool_name = nil + end + String(load_balancer_names).split(',').map do |load_balancer_name| + Bosh::AzureCloud::LoadBalancerConfig.new( + resource_group_name || global_azure_config.resource_group_name, + load_balancer_name, + backend_pool_name + ) + end + end + load_balancers.compact + end + + # @return [Array,nil] + def _parse_application_gateway_config(vm_properties) + application_gateway_config = vm_properties[APPLICATION_GATEWAY_KEY] + + return nil unless application_gateway_config + + cloud_error("Property '#{APPLICATION_GATEWAY_KEY}' must be a String, Hash, or Array.") unless application_gateway_config.is_a?(String) || application_gateway_config.is_a?(Hash) || application_gateway_config.is_a?(Array) + + application_gateway_configs = application_gateway_config.is_a?(Array) ? application_gateway_config : [application_gateway_config] + application_gateways = Array(application_gateway_configs).flat_map do |agwc| + if agwc.is_a?(Hash) + application_gateway_names = agwc[NAME_KEY] + resource_group_name = agwc[RESOURCE_GROUP_NAME_KEY] + backend_pool_name = agwc[BACKEND_POOL_NAME_KEY] + else + application_gateway_names = agwc + resource_group_name = nil + backend_pool_name = nil + end + String(application_gateway_names).split(',').map do |application_gateway_name| + Bosh::AzureCloud::ApplicationGatewayConfig.new( + # NOTE: It is OK for the resource_group_name to be `nil` here. The nil will be defaulted elsewhere (if needed). And leaving it nil makes the specs simpler. + resource_group_name, + application_gateway_name, + backend_pool_name + ) + end end + application_gateways.compact end + # @return [Bosh::AzureCloud::AvailabilitySetConfig] def _parse_availability_set_config(vm_properties, global_azure_config) if vm_properties[AVAILABILITY_SET_KEY].is_a?(Hash) platform_update_domain_count = vm_properties[AVAILABILITY_SET_KEY]['platform_update_domain_count'] || _default_update_domain_count(global_azure_config) diff --git a/src/bosh_azure_cpi/lib/cloud/azure/network/network_configurator.rb b/src/bosh_azure_cpi/lib/cloud/azure/network/network_configurator.rb index d0ded3bda..b3c45a741 100644 --- a/src/bosh_azure_cpi/lib/cloud/azure/network/network_configurator.rb +++ b/src/bosh_azure_cpi/lib/cloud/azure/network/network_configurator.rb @@ -9,7 +9,7 @@ module Bosh::AzureCloud # VM can have multiple network interfaces attached to it. # The VM size determines the number of NICs that you can create for a VM, please refer to # https://azure.microsoft.com/en-us/documentation/articles/virtual-machines-linux-sizes/ for the max number of NICs for different VM size. - # When there are multiple netowrks, you must have and only have 1 primary network specified. @networks[0] will be picked as the primary network. + # When there are multiple networks, you must have and only have 1 primary network specified. @networks[0] will be picked as the primary network. # class NetworkConfigurator @@ -64,6 +64,7 @@ def initialize(azure_config, spec) # unless network.nil? if network.has_default_gateway? + # make it the first network, so that it is the Primary @networks.insert(0, network) else @networks.push(network) diff --git a/src/bosh_azure_cpi/lib/cloud/azure/restapi/azure_client.rb b/src/bosh_azure_cpi/lib/cloud/azure/restapi/azure_client.rb index ce1415e25..3c9def434 100644 --- a/src/bosh_azure_cpi/lib/cloud/azure/restapi/azure_client.rb +++ b/src/bosh_azure_cpi/lib/cloud/azure/restapi/azure_client.rb @@ -302,6 +302,7 @@ def create_virtual_machine(resource_group_name, vm_params, network_interfaces, a network_interfaces_params.push( 'id' => network_interface[:id], 'properties' => { + # NOTE: The first NIC is the Primary/Gateway network. See: `Bosh::AzureCloud::NetworkConfigurator.initialize`. 'primary' => index.zero? } ) @@ -1334,13 +1335,13 @@ def delete_public_ip(resource_group_name, name) # Network/Load Balancer # Get a load balancer's information + # @param [String,nil] resource_group_name - The load balancer's resource group name. # @param [String] name - Name of load balancer. # # @return [Hash] # # @See https://docs.microsoft.com/en-us/rest/api/load-balancer/loadbalancers/get # - def get_load_balancer_by_name(resource_group_name, name) url = rest_api_url(REST_API_PROVIDER_NETWORK, REST_API_LOAD_BALANCERS, resource_group_name: resource_group_name, name: name) _get_load_balancer(url) @@ -1355,6 +1356,7 @@ def get_load_balancer_by_name(resource_group_name, name) # def _get_load_balancer(url) load_balancer = nil + # see: https://docs.microsoft.com/en-us/rest/api/load-balancer/load-balancers/get#loadbalancer result = get_resource_by_id(url) unless result.nil? load_balancer = {} @@ -1379,6 +1381,7 @@ def _get_load_balancer(url) load_balancer[:frontend_ip_configurations].push(ip) end + # see: https://docs.microsoft.com/en-us/rest/api/load-balancer/load-balancers/get#backendaddresspool backend = properties['backendAddressPools'] load_balancer[:backend_address_pools] = [] backend.each do |backend_ip| @@ -1417,8 +1420,8 @@ def _get_load_balancer(url) # * +:dns_servers - Array. DNS servers. # * +:network_security_group - Hash. The network security group which the network interface is bound to. # * +:application_security_groups - Array. The application security groups which the network interface is bound to. - # * +:load_balancer - Hash. The load balancer which the network interface is bound to. - # * +:application_gateway - Hash. The application gateway which the network interface is bound to. + # * +:load_balancers - Array. The load balancers which the network interface is bound to. (see: Bosh::AzureCloud::VMManager._get_load_balancers) + # * +:application_gateways - Array. The application gateways which the network interface is bound to. (see: Bosh::AzureCloud::VMManager._get_application_gateways) # # @return [Boolean] # @@ -1461,26 +1464,26 @@ def create_network_interface(resource_group_name, nic_params) end interface['properties']['ipConfigurations'][0]['properties']['applicationSecurityGroups'] = application_security_groups unless application_security_groups.empty? - load_balancer = nic_params[:load_balancer] - unless load_balancer.nil? - backend_pools = load_balancer.collect { |single_load_balancer| {:id => single_load_balancer[:backend_address_pools][0][:id]} } + # see: Bosh::AzureCloud::VMManager._get_load_balancers + load_balancers = nic_params[:load_balancers] + unless load_balancers.nil? + backend_pools = load_balancers.map { |load_balancer| {:id => load_balancer[:backend_address_pools][0][:id]} } inbound_nat_rules = Array.new - load_balancer.each do |single_load_balancer| - unless single_load_balancer[:frontend_ip_configurations][0][:inbound_nat_rules].nil? - inbound_nat_rules += single_load_balancer[:frontend_ip_configurations][0][:inbound_nat_rules] + load_balancers.each do |load_balancer| + unless load_balancer[:frontend_ip_configurations][0][:inbound_nat_rules].nil? + inbound_nat_rules += load_balancer[:frontend_ip_configurations][0][:inbound_nat_rules] end end interface['properties']['ipConfigurations'][0]['properties']['loadBalancerBackendAddressPools'] = backend_pools interface['properties']['ipConfigurations'][0]['properties']['loadBalancerInboundNatRules'] = inbound_nat_rules end - application_gateway = nic_params[:application_gateway] - unless application_gateway.nil? - interface['properties']['ipConfigurations'][0]['properties']['applicationGatewayBackendAddressPools'] = [ - { - 'id' => application_gateway[:backend_address_pools][0][:id] - } - ] + # see: Bosh::AzureCloud::VMManager._get_application_gateways + application_gateways = nic_params[:application_gateways] + unless application_gateways.nil? + # NOTE: backend_address_pools[0] should always be used. (When `application_gateway/backend_pool_name` is specified, the named pool will always be first here.) + backend_pools = application_gateways.map { |application_gateway| {:id => application_gateway[:backend_address_pools][0][:id]} } + interface['properties']['ipConfigurations'][0]['properties']['applicationGatewayBackendAddressPools'] = backend_pools end http_put(url, interface) @@ -1619,14 +1622,15 @@ def get_network_subnet(url) # Network/Application Gateway # Get an application gateway's information + # @param [String,nil] resource_group_name - The application gateway's resource group name. # @param [String] name - Name of application gateway. # # @return [Hash] # # @See https://docs.microsoft.com/en-us/rest/api/application-gateway/applicationgateways/get # - def get_application_gateway_by_name(name) - url = rest_api_url(REST_API_PROVIDER_NETWORK, REST_API_APPLICATION_GATEWAYS, name: name) + def get_application_gateway_by_name(resource_group_name, name) + url = rest_api_url(REST_API_PROVIDER_NETWORK, REST_API_APPLICATION_GATEWAYS, resource_group_name: resource_group_name, name: name) get_application_gateway(url) end @@ -1639,6 +1643,7 @@ def get_application_gateway_by_name(name) # def get_application_gateway(url) application_gateway = nil + # see: https://docs.microsoft.com/en-us/rest/api/application-gateway/application-gateways/get#applicationgateway result = get_resource_by_id(url) unless result.nil? application_gateway = {} @@ -1648,11 +1653,15 @@ def get_application_gateway(url) application_gateway[:tags] = result['tags'] properties = result['properties'] + # see: https://docs.microsoft.com/en-us/rest/api/application-gateway/application-gateways/get#applicationgatewaybackendaddresspool backend = properties['backendAddressPools'] application_gateway[:backend_address_pools] = [] backend.each do |backend_ip| ip = {} - ip[:id] = backend_ip['id'] + ip[:name] = backend_ip['name'] + ip[:id] = backend_ip['id'] + ip[:provisioning_state] = backend_ip['properties']['provisioningState'] + ip[:backend_ip_configurations] = backend_ip['properties']['backendIPConfigurations'] application_gateway[:backend_address_pools].push(ip) end end @@ -2057,21 +2066,31 @@ def parse_network_interface(result, recursive: true) { id: ip_configuration_properties['publicIPAddress']['id'] } end end - unless ip_configuration_properties['loadBalancerBackendAddressPools'].nil? - if recursive - names = _parse_name_from_id(ip_configuration_properties['loadBalancerBackendAddressPools'][0]['id']) - interface[:load_balancer] = get_load_balancer_by_name(names[:resource_group_name], names[:resource_name]) - else - interface[:load_balancer] = { id: ip_configuration_properties['loadBalancerBackendAddressPools'][0]['id'] } + load_balancer_backend_pools = ip_configuration_properties['loadBalancerBackendAddressPools'] + unless load_balancer_backend_pools.nil? + load_balancers = load_balancer_backend_pools.map do |lb_backend_pool| + if recursive + names = _parse_name_from_id(lb_backend_pool['id']) + load_balancer = get_load_balancer_by_name(names[:resource_group_name], names[:resource_name]) + else + load_balancer = { id: lb_backend_pool['id'] } + end + load_balancer end + interface[:load_balancers] = load_balancers end - unless ip_configuration_properties['applicationGatewayBackendAddressPools'].nil? - if recursive - names = _parse_name_from_id(ip_configuration_properties['applicationGatewayBackendAddressPools'][0]['id']) - interface[:application_gateway] = get_application_gateway_by_name(names[:resource_name]) - else - interface[:application_gateway] = { id: ip_configuration_properties['applicationGatewayBackendAddressPools'][0]['id'] } + application_gateway_backend_pools = ip_configuration_properties['applicationGatewayBackendAddressPools'] + unless application_gateway_backend_pools.nil? + application_gateways = application_gateway_backend_pools.map do |agw_backend_pool| + if recursive + names = _parse_name_from_id(agw_backend_pool['id']) + application_gateway = get_application_gateway_by_name(names[:resource_group_name], names[:resource_name]) + else + application_gateway = { id: agw_backend_pool['id'] } + end + application_gateway end + interface[:application_gateways] = application_gateways end unless ip_configuration_properties['applicationSecurityGroups'].nil? asgs_properties = ip_configuration_properties['applicationSecurityGroups'] diff --git a/src/bosh_azure_cpi/lib/cloud/azure/vms/vm_manager_network.rb b/src/bosh_azure_cpi/lib/cloud/azure/vms/vm_manager_network.rb index 27c231e91..fa059407c 100644 --- a/src/bosh_azure_cpi/lib/cloud/azure/vms/vm_manager_network.rb +++ b/src/bosh_azure_cpi/lib/cloud/azure/vms/vm_manager_network.rb @@ -88,28 +88,64 @@ def _get_public_ip(vip_network) public_ip end - def _get_load_balancer(vm_props) - load_balancer = nil - unless vm_props.load_balancer.name.nil? - load_balancer_name_split = vm_props.load_balancer.name.split(',') - load_balancer = Array.new - load_balancer_name_split.each do |load_balancer_name| - single_load_balancer = @azure_client.get_load_balancer_by_name(vm_props.load_balancer.resource_group_name, load_balancer_name) - cloud_error("Cannot find the load balancer '#{load_balancer_name}'") if single_load_balancer.nil? - load_balancer.push(single_load_balancer) + # @return [Array] + # @see Bosh::AzureCloud::AzureClient.create_network_interface + def _get_load_balancers(vm_props) + load_balancers = nil + load_balancer_configs = vm_props.load_balancers + unless load_balancer_configs.nil? + # NOTE: The following block gets the Azure API info for each Bosh AGW config (from the vm_type config). + load_balancers = load_balancer_configs.map do |load_balancer_config| + load_balancer = @azure_client.get_load_balancer_by_name(load_balancer_config.resource_group_name, load_balancer_config.name) + cloud_error("Cannot find the load balancer '#{load_balancer_config.name}'") if load_balancer.nil? + + if load_balancer_config.backend_pool_name + # NOTE: This is the only place where we simultaneously have both the Bosh LB config (from the vm_type) AND the Azure LB info (from the Azure API). + # Since the `AzureClient.create_network_interface` method only uses the first backend pool, + # and since there is not a 1-to-1 mapping between the vm_type LB configs and LBs (despite the config property name, each vm_type LB config actually represents a single LB Backend Pool, not a whole LB), + # we can therefore remove all pools EXCEPT the specified pool. + # To handle multiple pools of a single LB, you should use 1 vm_type LB Hash (with LB name + backend_pool_name) per pool. + pools = load_balancer[:backend_address_pools] + pools = [pools] unless pools.is_a?(Array) + pool = pools.find { |p| Hash(p)[:name] == load_balancer_config.backend_pool_name } + cloud_error("'#{load_balancer_config.name}' does not have a backend_pool named '#{load_balancer_config.backend_pool_name}': #{load_balancer}") if pool.nil? + + load_balancer[:backend_address_pools] = [pool] + end + load_balancer end end - load_balancer + load_balancers end - def _get_application_gateway(vm_props) - application_gateway = nil - unless vm_props.application_gateway.nil? - application_gateway_name = vm_props.application_gateway - application_gateway = @azure_client.get_application_gateway_by_name(application_gateway_name) - cloud_error("Cannot find the application gateway '#{application_gateway_name}'") if application_gateway.nil? + # @return [Array] + # @see Bosh::AzureCloud::AzureClient.create_network_interface + def _get_application_gateways(vm_props) + application_gateways = nil + application_gateway_configs = vm_props.application_gateways + unless application_gateway_configs.nil? + # NOTE: The following block gets the Azure API info for each Bosh AGW config (from the vm_type config). + application_gateways = application_gateway_configs.map do |application_gateway_config| + application_gateway = @azure_client.get_application_gateway_by_name(application_gateway_config.resource_group_name, application_gateway_config.name) + cloud_error("Cannot find the application gateway '#{application_gateway_config.name}'") if application_gateway.nil? + + if application_gateway_config.backend_pool_name + # NOTE: This is the only place where we simultaneously have both the Bosh AGW config (from the vm_type) AND the Azure AGW info (from the Azure API). + # Since the `AzureClient.create_network_interface` method only uses the first backend pool, + # and since there is not a 1-to-1 mapping between the vm_type AGW configs and AGWs (despite the config property name, each vm_type AGW config actually represents a single AGW Backend Pool, not a whole AGW), + # we can therefore remove all pools EXCEPT the specified pool. + # To handle multiple pools of a single AGW, you should use 1 vm_type AGW Hash (with AGW name + backend_pool_name) per pool. + pools = application_gateway[:backend_address_pools] + pools = [pools] unless pools.is_a?(Array) + pool = pools.find { |p| Hash(p)[:name] == application_gateway_config.backend_pool_name } + cloud_error("'#{application_gateway_config.name}' does not have a backend_pool named '#{application_gateway_config.backend_pool_name}': #{application_gateway}") if pool.nil? + + application_gateway[:backend_address_pools] = [pool] + end + application_gateway + end end - application_gateway + application_gateways end def _get_or_create_public_ip(resource_group_name, vm_name, location, vm_props, network_configurator) @@ -131,11 +167,12 @@ def _get_or_create_public_ip(resource_group_name, vm_name, location, vm_props, n public_ip end + # @return [Array] one Hash (returned by Bosh::AzureCloud::AzureClient.get_network_interface_by_name) per network interface created def _create_network_interfaces(resource_group_name, vm_name, location, vm_props, network_configurator, primary_nic_tags = AZURE_TAGS) # Tasks to prepare before creating NICs: - # * preapre public ip - # * prepare load balancer - # * prepare application gateway + # * prepare public ip + # * prepare load balancer(s) + # * prepare application gateway(s) tasks_preparing = [] tasks_preparing.push( @@ -144,13 +181,13 @@ def _create_network_interfaces(resource_group_name, vm_name, location, vm_props, end ) tasks_preparing.push( - task_get_load_balancer = Concurrent::Future.execute do - _get_load_balancer(vm_props) + task_get_load_balancers = Concurrent::Future.execute do + _get_load_balancers(vm_props) end ) tasks_preparing.push( - task_get_application_gateway = Concurrent::Future.execute do - _get_application_gateway(vm_props) + task_get_application_gateways = Concurrent::Future.execute do + _get_application_gateways(vm_props) end ) @@ -158,8 +195,8 @@ def _create_network_interfaces(resource_group_name, vm_name, location, vm_props, tasks_preparing.map(&:wait) public_ip = task_get_or_create_public_ip.value! - load_balancer = task_get_load_balancer.value! - application_gateway = task_get_application_gateway.value! + load_balancers = task_get_load_balancers.value! + application_gateways = task_get_application_gateways.value! # tasks to create NICs, NICs will be created in different threads tasks_creating = [] @@ -182,16 +219,17 @@ def _create_network_interfaces(resource_group_name, vm_name, location, vm_props, enable_accelerated_networking: accelerated_networking } nic_params[:subnet] = _get_network_subnet(network) + # NOTE: The first NIC is the Primary/Gateway network. See: `Bosh::AzureCloud::NetworkConfigurator.initialize`. if index.zero? nic_params[:public_ip] = public_ip nic_params[:tags] = primary_nic_tags - nic_params[:load_balancer] = load_balancer - nic_params[:application_gateway] = application_gateway + nic_params[:load_balancers] = load_balancers + nic_params[:application_gateways] = application_gateways else nic_params[:public_ip] = nil nic_params[:tags] = AZURE_TAGS - nic_params[:load_balancer] = nil - nic_params[:application_gateway] = nil + nic_params[:load_balancers] = nil + nic_params[:application_gateways] = nil end tasks_creating.push( Concurrent::Future.execute do diff --git a/src/bosh_azure_cpi/spec/integration/resources/application_gateway_spec.rb b/src/bosh_azure_cpi/spec/integration/resources/application_gateway_spec.rb index 0b2a1960d..0a1b5c33e 100644 --- a/src/bosh_azure_cpi/spec/integration/resources/application_gateway_spec.rb +++ b/src/bosh_azure_cpi/spec/integration/resources/application_gateway_spec.rb @@ -7,7 +7,7 @@ @application_gateway_name = ENV.fetch('BOSH_AZURE_APPLICATION_GATEWAY_NAME') end - context 'when application_gateway is specified in resource pool' do + context 'when single application_gateway is specified in resource pool' do let(:network_spec) do { 'network_a' => { diff --git a/src/bosh_azure_cpi/spec/unit/azure_client/create_network_interface_spec.rb b/src/bosh_azure_cpi/spec/unit/azure_client/create_network_interface_spec.rb index fd013c8a5..f89f56239 100644 --- a/src/bosh_azure_cpi/spec/unit/azure_client/create_network_interface_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/azure_client/create_network_interface_spec.rb @@ -55,8 +55,8 @@ public_ip: { id: 'fake-public-id' }, network_security_group: { id: nsg_id }, application_security_groups: [], - load_balancer: nil, - application_gateway: nil + load_balancers: nil, + application_gateways: nil } end let(:request_body) do @@ -130,8 +130,8 @@ enable_accelerated_networking: false, network_security_group: { id: nsg_id }, application_security_groups: [], - load_balancer: nil, - application_gateway: nil + load_balancers: nil, + application_gateways: nil } end let(:request_body) do @@ -208,8 +208,8 @@ public_ip: { id: 'fake-public-id' }, network_security_group: nil, application_security_groups: [], - load_balancer: nil, - application_gateway: nil + load_balancers: nil, + application_gateways: nil } end let(:request_body) do @@ -270,36 +270,6 @@ end context 'with load balancer' do - let(:nic_params) do - { - name: nic_name, - location: 'fake-location', - ipconfig_name: 'fake-ipconfig-name', - subnet: { id: subnet[:id] }, - tags: {}, - enable_ip_forwarding: false, - enable_accelerated_networking: false, - private_ip: '10.0.0.100', - dns_servers: ['168.63.129.16'], - public_ip: { id: 'fake-public-id' }, - network_security_group: { id: nsg_id }, - application_security_groups: [], - load_balancer: [{ - backend_address_pools: [ - { - id: 'fake-id' - } - ], - frontend_ip_configurations: [ - { - inbound_nat_rules: [{}] - } - ] - }], - application_gateway: nil - } - end - let(:request_body) do { name: nic_params[:name], @@ -320,12 +290,8 @@ subnet: { id: subnet[:id] }, - loadBalancerBackendAddressPools: [ - { - id: 'fake-id' - } - ], - loadBalancerInboundNatRules: [{}] + loadBalancerBackendAddressPools: nic_params[:load_balancers].map { |lb| { id: lb[:backend_address_pools][0][:id] } }, + loadBalancerInboundNatRules: nic_params[:load_balancers].flat_map { |lb| lb[:frontend_ip_configurations][0][:inbound_nat_rules] }.compact } }], dnsSettings: { @@ -335,7 +301,7 @@ } end - it 'should create a network interface without error' do + before do stub_request(:post, token_uri).to_return( status: 200, body: { @@ -358,10 +324,309 @@ body: '{"status":"Succeeded"}', headers: {} ) + end - expect do - azure_client.create_network_interface(resource_group, nic_params) - end.not_to raise_error + context 'with single load balancer' do + context 'with single backend pool' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: [{ + backend_address_pools: [ + { + name: 'fake-lb-pool-name', + id: 'fake-lb-pool-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + }], + application_gateways: nil + } + end + + it 'should create a network interface without error' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'with multiple backend pools' do + context 'when backend_pool_name is not specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: [{ + backend_address_pools: [ + { + name: 'fake-lb-pool-name', + id: 'fake-lb-pool-id' + }, + { + name: 'fake-lb-pool2-name', + id: 'fake-lb-pool2-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + }], + application_gateways: nil + } + end + + it 'should use the default backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'when backend_pool_name is specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + # NOTE: This data would normally be created by the `VMManager._get_load_balancers` method, + # which would remove all but the `vm_props`-configured pool from the list. + load_balancers: [{ + backend_address_pools: [ + { + name: 'fake-lb-pool2-name', + id: 'fake-lb-pool2-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + }], + application_gateways: nil + } + end + + it 'should use the specified backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + end + end + + context 'with multiple load balancers' do + context 'with single backend pool' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: [ + { + backend_address_pools: [ + { + name: 'fake-lb-pool-name', + id: 'fake-lb-pool-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + }, + { + backend_address_pools: [ + { + name: 'fake-lb2-pool-1-name', + id: 'fake-lb2-pool-1-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + } + ], + application_gateways: nil + } + end + + it 'should create a network interface without error' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'with multiple backend pools' do + context 'when backend_pool_name is not specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: [ + { + backend_address_pools: [ + { + name: 'fake-lb-pool-name', + id: 'fake-lb-pool-id' + }, + { + name: 'fake-lb-pool2-name', + id: 'fake-lb-pool2-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + }, + { + backend_address_pools: [ + { + name: 'fake-lb2-pool-1-name', + id: 'fake-lb2-pool-1-id' + }, + { + name: 'fake-lb2-pool-2-name', + id: 'fake-lb2-pool-2-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + } + ], + application_gateways: nil + } + end + + it 'should use the default backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'when backend_pool_name is specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + # NOTE: This data would normally be created by the `VMManager._get_load_balancers` method, + # which would remove all but the `vm_props`-configured pools from the list. + load_balancers: [ + { + backend_address_pools: [ + { + name: 'fake-lb-pool2-name', + id: 'fake-lb-pool2-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + }, + { + backend_address_pools: [ + { + name: 'fake-lb2-pool-2-name', + id: 'fake-lb2-pool-2-id' + } + ], + frontend_ip_configurations: [ + { + inbound_nat_rules: [{}] + } + ] + } + ], + application_gateways: nil + } + end + + it 'should use the specified backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + end end end @@ -380,8 +645,8 @@ public_ip: { id: 'fake-public-id' }, network_security_group: { id: nsg_id }, application_security_groups: [{ id: 'fake-asg-id-1' }, { id: 'fake-asg-id-2' }], - load_balancer: nil, - application_gateway: nil + load_balancers: nil, + application_gateways: nil } end let(:request_body) do @@ -452,30 +717,6 @@ end context 'with application gateway' do - let(:nic_params) do - { - name: nic_name, - location: 'fake-location', - ipconfig_name: 'fake-ipconfig-name', - subnet: { id: subnet[:id] }, - tags: {}, - enable_ip_forwarding: false, - enable_accelerated_networking: false, - private_ip: '10.0.0.100', - dns_servers: ['168.63.129.16'], - public_ip: { id: 'fake-public-id' }, - network_security_group: { id: nsg_id }, - application_security_groups: [], - load_balancer: nil, - application_gateway: { - backend_address_pools: [ - { - id: 'fake-id-2' - } - ] - } - } - end let(:request_body) do { name: nic_params[:name], @@ -496,11 +737,7 @@ subnet: { id: subnet[:id] }, - applicationGatewayBackendAddressPools: [ - { - id: 'fake-id-2' - } - ] + applicationGatewayBackendAddressPools: nic_params[:application_gateways].map { |agw| { id: agw[:backend_address_pools][0][:id] } } } }], dnsSettings: { @@ -510,7 +747,7 @@ } end - it 'should create a network interface without error' do + before do stub_request(:post, token_uri).to_return( status: 200, body: { @@ -533,10 +770,268 @@ body: '{"status":"Succeeded"}', headers: {} ) + end - expect do - azure_client.create_network_interface(resource_group, nic_params) - end.not_to raise_error + context 'with single application gateway' do + context 'with single backend pool' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: nil, + application_gateways: [{ + backend_address_pools: [ + { + name: 'fake-agw-pool-name', + id: 'fake-agw-pool-id' + } + ] + }] + } + end + + it 'should create a network interface without error' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'with multiple backend pools' do + context 'when backend_pool_name is not specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: nil, + application_gateways: [ + { + backend_address_pools: [ + { + name: 'fake-agw-pool-name', + id: 'fake-agw-pool-id' + }, + { + name: 'fake-agw-pool2-name', + id: 'fake-agw-pool2-id' + } + ] + } + ] + } + end + + it 'should use the default backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'when backend_pool_name is specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: nil, + # NOTE: This data would normally be created by the `VMManager._get_application_gateways` method, + # which would remove all but the `vm_props`-configured pool from the list. + application_gateways: [ + { + backend_address_pools: [ + { + name: 'fake-agw-pool2-name', + id: 'fake-agw-pool2-id' + } + ] + } + ] + } + end + + it 'should use the specified backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + end + end + + context 'with multiple application gateways' do + context 'with single backend pool' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: nil, + application_gateways: [ + { + backend_address_pools: [ + { + name: 'fake-agw-pool-name', + id: 'fake-agw-pool-id' + } + ] + }, + { + backend_address_pools: [ + { + name: 'fake-agw2-pool-1-name', + id: 'fake-agw2-pool-1-id' + } + ] + } + ] + } + end + + it 'should create a network interface without error' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'with multiple backend pools' do + context 'when backend_pool_name is not specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: nil, + application_gateways: [ + { + backend_address_pools: [ + { + name: 'fake-agw-pool-name', + id: 'fake-agw-pool-id' + }, + { + name: 'fake-agw-pool2-name', + id: 'fake-agw-pool2-id' + } + ] + }, + { + backend_address_pools: [ + { + name: 'fake-agw2-pool-1-name', + id: 'fake-agw2-pool-1-id' + }, + { + name: 'fake-agw2-pool-2-name', + id: 'fake-agw2-pool-2-id' + } + ] + } + ] + } + end + + it 'should use the default backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + + context 'when backend_pool_name is specified' do + let(:nic_params) do + { + name: nic_name, + location: 'fake-location', + ipconfig_name: 'fake-ipconfig-name', + subnet: { id: subnet[:id] }, + tags: {}, + enable_ip_forwarding: false, + enable_accelerated_networking: false, + private_ip: '10.0.0.100', + dns_servers: ['168.63.129.16'], + public_ip: { id: 'fake-public-id' }, + network_security_group: { id: nsg_id }, + application_security_groups: [], + load_balancers: nil, + # NOTE: This data would normally be created by the `VMManager._get_application_gateways` method, + # which would remove all but the `vm_props`-configured pools from the list. + application_gateways: [ + { + backend_address_pools: [ + { + name: 'fake-agw-pool2-name', + id: 'fake-agw-pool2-id' + } + ] + }, + { + backend_address_pools: [ + { + name: 'fake-agw2-pool-2-name', + id: 'fake-agw2-pool-2-id' + } + ] + } + ] + } + end + + it 'should use the specified backend_pools' do + expect do + azure_client.create_network_interface(resource_group, nic_params) + end.not_to raise_error + end + end + end end end end diff --git a/src/bosh_azure_cpi/spec/unit/azure_client/get_operation_spec.rb b/src/bosh_azure_cpi/spec/unit/azure_client/get_operation_spec.rb index acd990968..2c5baae7a 100644 --- a/src/bosh_azure_cpi/spec/unit/azure_client/get_operation_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/azure_client/get_operation_spec.rb @@ -183,7 +183,12 @@ 'properties' => { 'provisioningState' => 'fake-state', 'backendAddressPools' => [{ - 'id' => 'fake-id' + 'name' => 'fake-name', + 'id' => 'fake-id', + 'properties' => { + 'provisioningState' => 'fake-state', + 'backendIPConfigurations' => [] + } }] } }.to_json @@ -196,7 +201,10 @@ tags: 'fake-tags', backend_address_pools: [ { - id: 'fake-id' + name: 'fake-name', + id: 'fake-id', + provisioning_state: 'fake-state', + backend_ip_configurations: [] } ] } @@ -655,24 +663,44 @@ headers: {} ) expect( - azure_client.get_application_gateway_by_name(application_gateway_name) + azure_client.get_application_gateway_by_name(nil, application_gateway_name) ).to be_nil end - it 'should return the resource if response body is not null' do - stub_request(:get, application_gateway_uri).to_return( - status: 200, - body: application_gateway_response_body, - headers: {} - ) - stub_request(:get, public_ip_uri).to_return( - status: 200, - body: public_ip_response_body.to_json, - headers: {} - ) - expect( - azure_client.get_application_gateway_by_name(application_gateway_name) - ).to eq(fake_application_gateway) + context 'when resource_group_name is not specified' do + it 'should return the resource if response body is not null' do + stub_request(:get, application_gateway_uri).to_return( + status: 200, + body: application_gateway_response_body, + headers: {} + ) + stub_request(:get, public_ip_uri).to_return( + status: 200, + body: public_ip_response_body.to_json, + headers: {} + ) + expect( + azure_client.get_application_gateway_by_name(nil, application_gateway_name) + ).to eq(fake_application_gateway) + end + end + + context 'when resource_group_name is specified' do + it 'should return the resource if response body is not null' do + stub_request(:get, application_gateway_uri).to_return( + status: 200, + body: application_gateway_response_body, + headers: {} + ) + stub_request(:get, public_ip_uri).to_return( + status: 200, + body: public_ip_response_body.to_json, + headers: {} + ) + expect( + azure_client.get_application_gateway_by_name(default_resource_group_name, application_gateway_name) + ).to eq(fake_application_gateway) + end end end end @@ -816,7 +844,7 @@ ip_configuration_id: 'fake-id', private_ip: '10.0.0.100', private_ip_allocation_method: 'Dynamic', - load_balancer: fake_load_balancer + load_balancers: [fake_load_balancer] } end it 'should return the network interface with load balancer' do @@ -880,7 +908,7 @@ ip_configuration_id: 'fake-id', private_ip: '10.0.0.100', private_ip_allocation_method: 'Dynamic', - application_gateway: fake_application_gateway + application_gateways: [fake_application_gateway] } end it 'should return the network interface with application gateway' do diff --git a/src/bosh_azure_cpi/spec/unit/azure_client/list_network_interfaces_by_keyword_spec.rb b/src/bosh_azure_cpi/spec/unit/azure_client/list_network_interfaces_by_keyword_spec.rb index 0ad991922..e9f3d97d9 100644 --- a/src/bosh_azure_cpi/spec/unit/azure_client/list_network_interfaces_by_keyword_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/azure_client/list_network_interfaces_by_keyword_spec.rb @@ -167,8 +167,8 @@ private_ip_allocation_method: 'f0', network_security_group: { id: 'i' }, public_ip: { id: 'j' }, - load_balancer: { id: 'k' }, - application_gateway: { id: 'l' }, + load_balancers: [{ id: 'k' }], + application_gateways: [{ id: 'l' }], application_security_groups: [{ id: 'asg-id-1' }] } end diff --git a/src/bosh_azure_cpi/spec/unit/models/application_gateway_config_spec.rb b/src/bosh_azure_cpi/spec/unit/models/application_gateway_config_spec.rb new file mode 100644 index 000000000..ce22fa2e8 --- /dev/null +++ b/src/bosh_azure_cpi/spec/unit/models/application_gateway_config_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Bosh::AzureCloud::ApplicationGatewayConfig do + describe '#to_s' do + context 'when called' do + let(:name) { 'fake_name' } + let(:resource_group_name) { 'fake_rg' } + let(:backend_pool_name) { 'fake_pool' } + let(:expected_string) { "name: #{name}, resource_group_name: #{resource_group_name}, backend_pool_name: #{backend_pool_name}" } + let(:application_gateway_config) { Bosh::AzureCloud::ApplicationGatewayConfig.new(resource_group_name, name, backend_pool_name) } + + it 'should return the correct string' do + expect(application_gateway_config.to_s).to eq(expected_string) + end + end + end +end diff --git a/src/bosh_azure_cpi/spec/unit/models/load_balancer_config_spec.rb b/src/bosh_azure_cpi/spec/unit/models/load_balancer_config_spec.rb index 268193d03..0ebcbc98a 100644 --- a/src/bosh_azure_cpi/spec/unit/models/load_balancer_config_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/models/load_balancer_config_spec.rb @@ -7,11 +7,12 @@ context 'when called' do let(:name) { 'fake_name' } let(:resource_group_name) { 'fake_rg' } - let(:lb_string) { "name: #{name}, resource_group_name: #{resource_group_name}" } - let(:load_balancer_config) { Bosh::AzureCloud::LoadBalancerConfig.new(resource_group_name, name) } + let(:backend_pool_name) { 'fake_pool' } + let(:expected_string) { "name: #{name}, resource_group_name: #{resource_group_name}, backend_pool_name: #{backend_pool_name}" } + let(:load_balancer_config) { Bosh::AzureCloud::LoadBalancerConfig.new(resource_group_name, name, backend_pool_name) } it 'should return the correct string' do - expect(load_balancer_config.to_s).to eq(lb_string) + expect(load_balancer_config.to_s).to eq(expected_string) end end end diff --git a/src/bosh_azure_cpi/spec/unit/models/vm_cloud_props_spec.rb b/src/bosh_azure_cpi/spec/unit/models/vm_cloud_props_spec.rb index ae56ba27e..83ddde721 100644 --- a/src/bosh_azure_cpi/spec/unit/models/vm_cloud_props_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/models/vm_cloud_props_spec.rb @@ -90,9 +90,67 @@ end end + context 'when load_balancer is not specified' do + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1' + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.load_balancers).to be_nil + end + end + + context 'when load_balancer is a string' do + let(:lb_name) { 'fake_lb_name' } + + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'load_balancer' => lb_name + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.load_balancers.length).to eq(1) + load_balancer = vm_cloud_props.load_balancers.first + expect(load_balancer.name).to eq(lb_name) + expect(load_balancer.resource_group_name).to eq(azure_config_managed.resource_group_name) + end + end + + context 'when load_balancer is a comma-delimited string' do + let(:lb_name) { 'fake_lb_name' } + + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'load_balancer' => "#{lb_name},b,c" + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.load_balancers.length).to eq(3) + expect(vm_cloud_props.load_balancers[0].name).to eq(lb_name) + expect(vm_cloud_props.load_balancers[0].resource_group_name).to eq(azure_config_managed.resource_group_name) + expect(vm_cloud_props.load_balancers[1].name).to eq('b') + expect(vm_cloud_props.load_balancers[1].resource_group_name).to eq(azure_config_managed.resource_group_name) + expect(vm_cloud_props.load_balancers[2].name).to eq('c') + expect(vm_cloud_props.load_balancers[2].resource_group_name).to eq(azure_config_managed.resource_group_name) + end + end + context 'when load_balancer is a hash' do let(:lb_name) { 'fake_lb_name' } let(:resource_group_name) { 'fake_resource_group' } + context 'when resource group not empty' do let(:vm_cloud_props) do Bosh::AzureCloud::VMCloudProps.new( @@ -107,8 +165,10 @@ end it 'should return the correct config' do - expect(vm_cloud_props.load_balancer.name).to eq(lb_name) - expect(vm_cloud_props.load_balancer.resource_group_name).to eq(resource_group_name) + expect(vm_cloud_props.load_balancers.length).to eq(1) + load_balancer = vm_cloud_props.load_balancers.first + expect(load_balancer.name).to eq(lb_name) + expect(load_balancer.resource_group_name).to eq(resource_group_name) end end @@ -125,10 +185,236 @@ end it 'should return the correct config' do - expect(vm_cloud_props.load_balancer.name).to eq(lb_name) - expect(vm_cloud_props.load_balancer.resource_group_name).to eq(azure_config_managed.resource_group_name) + expect(vm_cloud_props.load_balancers.length).to eq(1) + load_balancer = vm_cloud_props.load_balancers.first + expect(load_balancer.name).to eq(lb_name) + expect(load_balancer.resource_group_name).to eq(azure_config_managed.resource_group_name) end end + + context 'when name is a comma-delimited string' do + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'load_balancer' => { + 'name' => "#{lb_name},b,c", + 'resource_group_name' => resource_group_name + } + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.load_balancers.length).to eq(3) + expect(vm_cloud_props.load_balancers[0].name).to eq(lb_name) + expect(vm_cloud_props.load_balancers[0].resource_group_name).to eq(resource_group_name) + expect(vm_cloud_props.load_balancers[1].name).to eq('b') + expect(vm_cloud_props.load_balancers[1].resource_group_name).to eq(resource_group_name) + expect(vm_cloud_props.load_balancers[2].name).to eq('c') + expect(vm_cloud_props.load_balancers[2].resource_group_name).to eq(resource_group_name) + end + end + end + + context 'when load_balancer is an array' do + let(:resource_group_name) { 'fake_resource_group' } + + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'load_balancer' => [ + 'fake_lb1_name', # String + { + 'name' => 'fake_lb2_name' + }, # Hash without resource_group_name + 'fake_lb3_name,fake_lb4_name', # delimited String + { + 'name' => 'fake_lb5_name,fake_lb6_name', + 'resource_group_name' => resource_group_name + } # Hash with delimited String and explicit resource_group_name + ] + }, azure_config_managed + ) + end + + it 'should return the correct config' do + load_balancers = vm_cloud_props.load_balancers + expect(load_balancers.length).to eq(6) + + expect(load_balancers[0].name).to eq('fake_lb1_name') + expect(load_balancers[0].resource_group_name).to eq(azure_config_managed.resource_group_name) + + expect(load_balancers[1].name).to eq('fake_lb2_name') + expect(load_balancers[1].resource_group_name).to eq(azure_config_managed.resource_group_name) + + expect(load_balancers[2].name).to eq('fake_lb3_name') + expect(load_balancers[2].resource_group_name).to eq(azure_config_managed.resource_group_name) + + expect(load_balancers[3].name).to eq('fake_lb4_name') + expect(load_balancers[3].resource_group_name).to eq(azure_config_managed.resource_group_name) + + expect(load_balancers[4].name).to eq('fake_lb5_name') + expect(load_balancers[4].resource_group_name).to eq(resource_group_name) + + expect(load_balancers[5].name).to eq('fake_lb6_name') + expect(load_balancers[5].resource_group_name).to eq(resource_group_name) + end + end + + context 'when load_balancer is an int' do + let(:vm_cloud_properties) do + { + 'load_balancer' => 123, + 'instance_type' => 't' + } + end + + it 'should raise an error' do + expect do + Bosh::AzureCloud::VMCloudProps.new(vm_cloud_properties, azure_config_managed) + end.to raise_error('Property \'load_balancer\' must be a String, Hash, or Array.') + end + end + + context 'when application_gateway is not specified' do + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1' + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.application_gateways).to be_nil + end + end + + context 'when application_gateway is a string' do + let(:agw_name) { 'fake_agw_name' } + + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'application_gateway' => agw_name + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.application_gateways.length).to eq(1) + application_gateway = vm_cloud_props.application_gateways.first + expect(application_gateway.name).to eq(agw_name) + expect(application_gateway.resource_group_name).to be_nil + end + end + + context 'when application_gateway is a hash' do + let(:agw_name) { 'fake_agw_name' } + + context 'when resource group not empty' do + let(:resource_group_name) { 'fake_resource_group' } + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'application_gateway' => { + 'name' => agw_name, + 'resource_group_name' => resource_group_name + } + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.application_gateways.length).to eq(1) + application_gateway = vm_cloud_props.application_gateways.first + expect(application_gateway.name).to eq(agw_name) + expect(application_gateway.resource_group_name).to eq(resource_group_name) + end + end + + context 'when resource group empty' do + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'application_gateway' => { 'name' => agw_name } + }, azure_config_managed + ) + end + + it 'should return the correct config' do + expect(vm_cloud_props.application_gateways.length).to eq(1) + application_gateway = vm_cloud_props.application_gateways.first + expect(application_gateway.name).to eq(agw_name) + expect(application_gateway.resource_group_name).to be_nil + end + end + end + + context 'when application_gateway is an array' do + let(:resource_group_name) { 'fake_resource_group' } + + let(:vm_cloud_props) do + Bosh::AzureCloud::VMCloudProps.new( + { + 'instance_type' => 'Standard_D1', + 'application_gateway' => [ + 'fake_agw1_name', # String + { + 'name' => 'fake_agw2_name' + }, # Hash without resource_group_name + 'fake_agw3_name,fake_agw4_name', # delimited String + { + 'name' => 'fake_agw5_name,fake_agw6_name', + 'resource_group_name' => resource_group_name + } # Hash with delimited String and explicit resource_group_name + ] + }, azure_config_managed + ) + end + + it 'should return the correct config' do + application_gateways = vm_cloud_props.application_gateways + expect(application_gateways.length).to eq(6) + + expect(application_gateways[0].name).to eq('fake_agw1_name') + expect(application_gateways[0].resource_group_name).to be_nil + + expect(application_gateways[1].name).to eq('fake_agw2_name') + expect(application_gateways[1].resource_group_name).to be_nil + + expect(application_gateways[2].name).to eq('fake_agw3_name') + expect(application_gateways[2].resource_group_name).to be_nil + + expect(application_gateways[3].name).to eq('fake_agw4_name') + expect(application_gateways[3].resource_group_name).to be_nil + + expect(application_gateways[4].name).to eq('fake_agw5_name') + expect(application_gateways[4].resource_group_name).to eq(resource_group_name) + + expect(application_gateways[5].name).to eq('fake_agw6_name') + expect(application_gateways[5].resource_group_name).to eq(resource_group_name) + end + end + + context 'when application_gateway is an int' do + let(:vm_cloud_properties) do + { + 'application_gateway' => 123, + 'instance_type' => 't' + } + end + + it 'should raise an error' do + expect do + Bosh::AzureCloud::VMCloudProps.new(vm_cloud_properties, azure_config_managed) + end.to raise_error('Property \'application_gateway\' must be a String, Hash, or Array.') + end end context '#managed_identity' do diff --git a/src/bosh_azure_cpi/spec/unit/vm_manager/create/application_security_group_spec.rb b/src/bosh_azure_cpi/spec/unit/vm_manager/create/application_security_group_spec.rb index a67c18283..fb3f78f29 100644 --- a/src/bosh_azure_cpi/spec/unit/vm_manager/create/application_security_group_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/vm_manager/create/application_security_group_spec.rb @@ -597,8 +597,8 @@ public_ip: dynamic_public_ip, subnet: subnet, tags: tags, - load_balancer: [ load_balancer ], - application_gateway: application_gateway + load_balancers: [ load_balancer ], + application_gateways: [application_gateway] )).once _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) @@ -625,8 +625,8 @@ public_ip: dynamic_public_ip, subnet: subnet, tags: tags, - load_balancer: [ load_balancer ], - application_gateway: application_gateway + load_balancers: [ load_balancer ], + application_gateways: [application_gateway] )) _, vm_params = vm_manager.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) diff --git a/src/bosh_azure_cpi/spec/unit/vm_manager/create/dynamic_public_ip_spec.rb b/src/bosh_azure_cpi/spec/unit/vm_manager/create/dynamic_public_ip_spec.rb index 998dd3664..b8b784f7f 100644 --- a/src/bosh_azure_cpi/spec/unit/vm_manager/create/dynamic_public_ip_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/vm_manager/create/dynamic_public_ip_spec.rb @@ -58,21 +58,467 @@ .and_return(name: storage_account_name) end - it 'creates a public IP and assigns it to the primary NIC' do - expect(azure_client).to receive(:create_public_ip) - .with(MOCK_RESOURCE_GROUP_NAME, public_ip_params) - expect(azure_client).to receive(:create_network_interface) - .with(MOCK_RESOURCE_GROUP_NAME, hash_including( - name: "#{vm_name}-0", - public_ip: dynamic_public_ip, - subnet: subnet, - tags: tags, - load_balancer: [ load_balancer ], - application_gateway: application_gateway - )).once + context 'with valid load_balancer config' do + context 'with single load_balancer' do + before do + expect(azure_client).to receive(:create_public_ip) + .with(MOCK_RESOURCE_GROUP_NAME, public_ip_params) + expect(azure_client).to receive(:create_network_interface) + .with(MOCK_RESOURCE_GROUP_NAME, hash_including( + name: "#{vm_name}-0", + public_ip: dynamic_public_ip, + subnet: subnet, + tags: tags, + load_balancers: [ load_balancer ], + application_gateways: [application_gateway] + )).once + end - _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) - expect(vm_params[:name]).to eq(vm_name) + it 'creates a public IP and assigns it to the primary NIC' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the NIC correctly. + end + + context 'when load_balancer has multiple backend pools' do + let(:load_balancer) do + { + name: 'fake-lb-name', + backend_address_pools: [ + { + name: 'fake-pool-name', + id: 'fake-pool-id', + provisioning_state: 'fake-pool-state', + backend_ip_configurations: [] + }, + { + name: 'fake-pool2-name', + id: 'fake-pool2-id', + provisioning_state: 'fake-pool2-state', + backend_ip_configurations: [] + } + ] + } + end + + context 'when backend_pool_name is not specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => { + 'name' => 'fake-lb-name' + }, + 'application_gateway' => 'fake-ag-name' + } + end + + it 'adds the public IP to the default pool' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the correct pool(s). + end + end + + context 'when backend_pool_name is specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => { + 'name' => 'fake-lb-name', + 'backend_pool_name' => 'fake-pool2-name' + }, + 'application_gateway' => 'fake-ag-name' + } + end + + it 'adds the public IP to the specified pool' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the correct pool(s). + end + end + end + end + + context 'with multiple load_balancers' do + let(:load_balancer) do + [ + { + name: 'fake-lb-name', + backend_address_pools: [ + { + name: 'fake-pool-name', + id: 'fake-pool-id', + provisioning_state: 'fake-pool-state', + backend_ip_configurations: [] + }, + { + name: 'fake-pool2-name', + id: 'fake-pool2-id', + provisioning_state: 'fake-pool2-state', + backend_ip_configurations: [] + } + ] + }, + { + name: 'fake-lb2-name', + backend_address_pools: [ + { + name: 'fake-lb2-pool-1-name', + id: 'fake-lb2-pool-1-id', + provisioning_state: 'fake-lb2-pool-1-state', + backend_ip_configurations: [] + }, + { + name: 'fake-lb2-pool-2-name', + id: 'fake-lb2-pool-2-id', + provisioning_state: 'fake-lb2-pool-2-state', + backend_ip_configurations: [] + } + ] + } + ] + end + + before do + expect(azure_client).to receive(:create_public_ip) + .with(MOCK_RESOURCE_GROUP_NAME, public_ip_params) + expect(azure_client).to receive(:create_network_interface) + .with(MOCK_RESOURCE_GROUP_NAME, hash_including( + name: "#{vm_name}-0", + public_ip: dynamic_public_ip, + subnet: subnet, + tags: tags, + load_balancers: load_balancer, + application_gateways: [application_gateway] + )).once + allow(azure_client).to receive(:get_load_balancer_by_name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) + .and_return(load_balancer.first) + allow(azure_client).to receive(:get_load_balancer_by_name) + .with(vm_props.load_balancers[1].resource_group_name, vm_props.load_balancers[1].name) + .and_return(load_balancer[1]) + end + + context 'when backend_pool_name is not specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => [ + { + 'name' => 'fake-lb-name' + }, + { + 'name' => 'fake-lb2-name' + } + ], + 'application_gateway' => 'fake-ag-name' + } + end + + it 'adds the public IP to the default pool of each load_balancer' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IPs was assigned to the correct pool(s). + end + end + + context 'when backend_pool_name is specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => [ + { + 'name' => 'fake-lb-name', + 'backend_pool_name' => 'fake-pool2-name' + }, + { + 'name' => 'fake-lb2-name', + 'backend_pool_name' => 'fake-lb2-pool-2-name' + } + ], + 'application_gateway' => 'fake-ag-name' + } + end + + it 'adds the public IP to the specified pool of each load_balancer' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the correct pool(s). + end + end + end + end + + context 'with invalid load_balancer config' do + context 'when an invalid backend_pool_name is specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => { + 'name' => 'fake-lb-name', + 'backend_pool_name' => 'invalid-pool-name' + }, + 'application_gateway' => 'fake-ag-name' + } + end + + it 'should raise an error' do + expect(azure_client).to receive(:create_public_ip) + .with(MOCK_RESOURCE_GROUP_NAME, public_ip_params) + expect(azure_client).to receive(:delete_public_ip).with(MOCK_RESOURCE_GROUP_NAME, vm_name) + # NOTE: For some reason, the following mock is needed when a `cloud_error` is raised + allow(azure_client).to receive(:list_network_interfaces_by_keyword) + .with(MOCK_RESOURCE_GROUP_NAME, vm_name) + .and_return([]).once + + expect do + vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + end.to raise_error(/('fake-lb-name' does not have a backend_pool named 'invalid-pool-name': \{:name=>"fake-lb-name", :backend_address_pools=>\[\{:name=>"fake-pool-name", :id=>"fake-pool-id", ).*/) + end + end + end + + context 'with valid application_gateway config' do + context 'with single application_gateway' do + before do + expect(azure_client).to receive(:create_public_ip) + .with(MOCK_RESOURCE_GROUP_NAME, public_ip_params) + expect(azure_client).to receive(:create_network_interface) + .with(MOCK_RESOURCE_GROUP_NAME, hash_including( + name: "#{vm_name}-0", + public_ip: dynamic_public_ip, + subnet: subnet, + tags: tags, + load_balancers: [ load_balancer ], + application_gateways: [application_gateway] + )).once + end + + # NOTE: The following spec is currently identical/redundant to the '.../with single load_balancer/creates a public IP and assigns it to the primary NIC' spec above. Ideally, each of them SHOULD have distinct expectations, but currently they don't. + it 'creates a public IP and assigns it to the primary NIC' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the NIC correctly. + end + + context 'when application_gateway has multiple backend pools' do + let(:application_gateway) do + { + name: 'fake-ag-name', + backend_address_pools: [ + { + name: 'fake-pool-name', + id: 'fake-pool-id', + provisioning_state: 'fake-pool-state', + backend_ip_configurations: [] + }, + { + name: 'fake-pool2-name', + id: 'fake-pool2-id', + provisioning_state: 'fake-pool2-state', + backend_ip_configurations: [] + } + ] + } + end + + context 'when backend_pool_name is not specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => 'fake-lb-name', + 'application_gateway' => { + 'name' => 'fake-ag-name' + } + } + end + + it 'adds the public IP to the default pool' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the correct pool(s). + end + end + + context 'when backend_pool_name is specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => 'fake-lb-name', + 'application_gateway' => { + 'name' => 'fake-ag-name', + 'backend_pool_name' => 'fake-pool2-name' + } + } + end + + it 'adds the public IP to the specified pool' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the correct pool(s). + end + end + end + end + + context 'with multiple application_gateways' do + let(:application_gateway) do + [ + { + name: 'fake-ag-name', + backend_address_pools: [ + { + name: 'fake-pool-name', + id: 'fake-pool-id', + provisioning_state: 'fake-pool-state', + backend_ip_configurations: [] + }, + { + name: 'fake-pool2-name', + id: 'fake-pool2-id', + provisioning_state: 'fake-pool2-state', + backend_ip_configurations: [] + } + ] + }, + { + name: 'fake-ag2-name', + backend_address_pools: [ + { + name: 'fake-ag2-pool-1-name', + id: 'fake-ag2-pool-1-id', + provisioning_state: 'fake-ag2-pool-1-state', + backend_ip_configurations: [] + }, + { + name: 'fake-ag2-pool-2-name', + id: 'fake-ag2-pool-2-id', + provisioning_state: 'fake-ag2-pool-2-state', + backend_ip_configurations: [] + } + ] + } + ] + end + + before do + expect(azure_client).to receive(:create_public_ip) + .with(MOCK_RESOURCE_GROUP_NAME, public_ip_params) + expect(azure_client).to receive(:create_network_interface) + .with(MOCK_RESOURCE_GROUP_NAME, hash_including( + name: "#{vm_name}-0", + public_ip: dynamic_public_ip, + subnet: subnet, + tags: tags, + load_balancers: [ load_balancer ], + application_gateways: application_gateway + )).once + allow(azure_client).to receive(:get_application_gateway_by_name) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) + .and_return(application_gateway.first) + allow(azure_client).to receive(:get_application_gateway_by_name) + .with(vm_props.application_gateways[1].resource_group_name, vm_props.application_gateways[1].name) + .and_return(application_gateway[1]) + end + + context 'when backend_pool_name is not specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => 'fake-lb-name', + 'application_gateway' => [ + { + 'name' => 'fake-ag-name' + }, + { + 'name' => 'fake-ag2-name' + } + ] + } + end + + it 'adds the public IP to the default pool of each application_gateway' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the correct pool(s). + end + end + + context 'when backend_pool_name is specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => 'fake-lb-name', + 'application_gateway' => [ + { + 'name' => 'fake-ag-name', + 'backend_pool_name' => 'fake-pool2-name' + }, + { + 'name' => 'fake-ag2-name', + 'backend_pool_name' => 'fake-ag2-pool-2-name' + } + ] + } + end + + it 'adds the public IP to the specified pool of each application_gateway' do + _, vm_params = vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the correct pool(s). + end + end + end + end + + context 'with invalid application_gateway config' do + context 'when an invalid backend_pool_name is specified' do + let(:vm_properties) do + { + 'instance_type' => 'Standard_D1', + 'storage_account_name' => 'dfe03ad623f34d42999e93ca', + 'caching' => 'ReadWrite', + 'load_balancer' => 'fake-lb-name', + 'application_gateway' => { + 'name' => 'fake-ag-name', + 'backend_pool_name' => 'invalid-pool-name' + } + } + end + + it 'should raise an error' do + expect(azure_client).to receive(:create_public_ip) + .with(MOCK_RESOURCE_GROUP_NAME, public_ip_params) + expect(azure_client).to receive(:delete_public_ip).with(MOCK_RESOURCE_GROUP_NAME, vm_name) + # NOTE: For some reason, the following mock is needed when a `cloud_error` is raised + allow(azure_client).to receive(:list_network_interfaces_by_keyword) + .with(MOCK_RESOURCE_GROUP_NAME, vm_name) + .and_return([]).once + + expect do + vm_manager_for_pip.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) + end.to raise_error(/('fake-ag-name' does not have a backend_pool named 'invalid-pool-name': \{:name=>"fake-ag-name", :backend_address_pools=>\[\{:name=>"fake-pool-name", :id=>"fake-pool-id", ).*/) + end + end end end @@ -95,12 +541,13 @@ public_ip: dynamic_public_ip, subnet: subnet, tags: tags, - load_balancer: [ load_balancer ], - application_gateway: application_gateway + load_balancers: [ load_balancer ], + application_gateways: [application_gateway] )) _, vm_params = vm_manager.create(bosh_vm_meta, location, vm_props, disk_cids, network_configurator, env, agent_util, network_spec, config) expect(vm_params[:name]).to eq(vm_name) + # TODO: Add more expectations here? The expects above only verify that the VM was created, but not that the IP was assigned to the NIC correctly. end end end diff --git a/src/bosh_azure_cpi/spec/unit/vm_manager/create/invalid_option_spec.rb b/src/bosh_azure_cpi/spec/unit/vm_manager/create/invalid_option_spec.rb index a3b16f213..f9d3f8bff 100644 --- a/src/bosh_azure_cpi/spec/unit/vm_manager/create/invalid_option_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/vm_manager/create/invalid_option_spec.rb @@ -22,10 +22,10 @@ .with(MOCK_RESOURCE_GROUP_NAME, vm_name) .and_return([]) allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_props.application_gateway) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) .and_return(application_gateway) allow(azure_client).to receive(:list_public_ips) .and_return([{ @@ -49,10 +49,10 @@ .with(MOCK_RESOURCE_GROUP_NAME, vm_name) .and_return([]) allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_props.application_gateway) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) .and_return(application_gateway) allow(azure_client).to receive(:list_public_ips) .and_return([{ @@ -79,10 +79,10 @@ allow(manual_network).to receive(:resource_group_name) .and_return('fake-resource-group-name') allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_props.application_gateway) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) .and_return(application_gateway) allow(azure_client).to receive(:list_public_ips) .and_return([{ @@ -128,10 +128,10 @@ context 'when public ip is not found' do before do allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_props.application_gateway) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) .and_return(application_gateway) end @@ -190,7 +190,7 @@ it 'should raise an error' do allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(nil) expect(azure_client).not_to receive(:delete_virtual_machine) @@ -211,10 +211,10 @@ it 'should raise an error' do allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_props.application_gateway) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) .and_return(nil) expect(azure_client).not_to receive(:delete_virtual_machine) @@ -231,10 +231,10 @@ allow(azure_client).to receive(:get_network_subnet_by_name) .and_return(subnet) allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_props.application_gateway) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) .and_return(application_gateway) allow(azure_client).to receive(:list_public_ips) .and_return([{ @@ -275,10 +275,10 @@ allow(azure_client).to receive(:get_network_subnet_by_name) .and_return(subnet) allow(azure_client).to receive(:get_load_balancer_by_name) - .with(vm_props.load_balancer.resource_group_name, vm_props.load_balancer.name) + .with(vm_props.load_balancers.first.resource_group_name, vm_props.load_balancers.first.name) .and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_props.application_gateway) + .with(vm_props.application_gateways.first.resource_group_name, vm_props.application_gateways.first.name) .and_return(application_gateway) allow(azure_client).to receive(:list_public_ips) .and_return([{ diff --git a/src/bosh_azure_cpi/spec/unit/vm_manager/create/shared_stuff.rb b/src/bosh_azure_cpi/spec/unit/vm_manager/create/shared_stuff.rb index 2c6a3afe1..0562a38c2 100644 --- a/src/bosh_azure_cpi/spec/unit/vm_manager/create/shared_stuff.rb +++ b/src/bosh_azure_cpi/spec/unit/vm_manager/create/shared_stuff.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# The following default configurations are shared. If one case needs a specific configuration, it should overwrite it. +# NOTE: The following default configurations are shared. If one case needs a specific configuration, it should overwrite it. shared_context 'shared stuff for vm manager' do # Parameters of VMManager.initialize let(:azure_config) { mock_azure_config } @@ -111,12 +111,28 @@ end let(:load_balancer) do { - name: 'fake-lb-name' + name: 'fake-lb-name', + backend_address_pools: [ + { + name: 'fake-pool-name', + id: 'fake-pool-id', + provisioning_state: 'fake-pool-state', + backend_ip_configurations: [] + } + ] } end let(:application_gateway) do { - name: 'fake-ag-name' + name: 'fake-ag-name', + backend_address_pools: [ + { + name: 'fake-pool-name', + id: 'fake-pool-id', + provisioning_state: 'fake-pool-state', + backend_ip_configurations: [] + } + ] } end before do @@ -128,12 +144,30 @@ allow(azure_client).to receive(:get_network_security_group_by_name) .with(MOCK_RESOURCE_GROUP_NAME, MOCK_DEFAULT_SECURITY_GROUP) .and_return(default_security_group) + load_balancer_config = vm_properties['load_balancer'] + load_balancer_name = load_balancer_config.is_a?(Hash) ? load_balancer_config['name'] : load_balancer_config + load_balancer_rg = load_balancer_config.is_a?(Hash) ? load_balancer_config['resource_group_name'] : nil + load_balancer_rg ||= MOCK_RESOURCE_GROUP_NAME allow(azure_client).to receive(:get_load_balancer_by_name) - .with(MOCK_RESOURCE_GROUP_NAME, vm_properties['load_balancer']) + .with(load_balancer_rg, load_balancer_name) .and_return(load_balancer) + application_gateway_config = vm_properties['application_gateway'] + application_gateway_name = application_gateway_config.is_a?(Hash) ? application_gateway_config['name'] : application_gateway_config + application_gateway_rg = application_gateway_config.is_a?(Hash) ? application_gateway_config['resource_group_name'] : nil allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_properties['application_gateway']) + .with(application_gateway_rg, application_gateway_name) .and_return(application_gateway) + # NOTE: If the `:vm_properties` var's `application_gateway/backend_pool_name` doesn't match the `:application_gateway` data's pools, + # then the mock below would be needed to enable usable rspec stack traces. + # Without this mock, the output gives `... received unexpected message :list_network_interfaces_by_keyword with ...` + # which doesn't include the actual error which was raised. Not very useful when debugging spec failures. + # Uncommenting the mock below doesn't SEEM to harm anything, + # even when the spec shared data is correct (and an error like the one mentioned above is NOT raised). + # However, since I'm not CERTAIN it wouldn't cause problems (e.g. if specific specs need a different return value), + # I've left it commented out for now. + # allow(azure_client).to receive(:list_network_interfaces_by_keyword) + # .with(MOCK_RESOURCE_GROUP_NAME, vm_name) + # .and_return([]) # .and_return(network_interfaces) allow(azure_client).to receive(:get_public_ip_by_name) .with(MOCK_RESOURCE_GROUP_NAME, vm_name) .and_return(nil) diff --git a/src/bosh_azure_cpi/spec/unit/vm_manager/delete_spec.rb b/src/bosh_azure_cpi/spec/unit/vm_manager/delete_spec.rb index c97361828..b2235e8f5 100644 --- a/src/bosh_azure_cpi/spec/unit/vm_manager/delete_spec.rb +++ b/src/bosh_azure_cpi/spec/unit/vm_manager/delete_spec.rb @@ -57,7 +57,7 @@ allow(azure_client).to receive(:get_load_balancer_by_name) .with(resource_group_name, vm_name).and_return(load_balancer) allow(azure_client).to receive(:get_application_gateway_by_name) - .with(vm_name).and_return(application_gateway) + .with(nil, vm_name).and_return(application_gateway) allow(azure_client).to receive(:get_network_interface_by_name) .with(resource_group_name, vm_name).and_return(network_interface) allow(azure_client).to receive(:get_public_ip_by_name)