ManageIQ/manageiq-providers-openstack

View on GitHub
app/models/manageiq/providers/openstack/network_manager/cloud_network.rb

Summary

Maintainability
C
1 day
Test Coverage
D
60%
require 'uri'
require 'net/http'

class ManageIQ::Providers::Openstack::NetworkManager::CloudNetwork < ::CloudNetwork
  include ManageIQ::Providers::Openstack::HelperMethods
  include SupportsFeatureMixin

  supports :create

  supports :delete do
    if ext_management_system.nil?
      _("The Cloud Network is not connected to an active %{table}") % {
        :table => ui_lookup(:table => "ext_management_systems")
      }
    end
  end

  supports :update do
    if ext_management_system.nil?
      _("The Cloud Network is not connected to an active %{table}") % {
        :table => ui_lookup(:table => "ext_management_systems")
      }
    end
  end

  def self.params_for_create(ems)
    {
      :fields => [
        {
          :component => 'sub-form',
          :title     => _('Placement'),
          :id        => 'placement',
          :name      => 'placement',
          :fields    => [
            {
              :component       => 'select',
              :id              => 'cloud_tenant',
              :name            => 'cloud_tenant',
              :key             => "id-#{ems.id}",
              :label           => _('Cloud Tenant'),
              :placeholder     => "<#{_('Choose')}>",
              :validateOnMount => true,
              :validate        => [{
                :type    => 'required',
                :message => _('Required'),
              }],
              :options         => ems.cloud_tenants.map do |ct|
                {
                  :label => ct.name,
                  :value => ct.id.to_s,
                }
              end,
              :includeEmpty    => true,
              :clearOnUnmount  => true,
            },
          ]
        },
        {
          :component => 'sub-form',
          :title     => _('Network Provider Information'),
          :id        => 'provider-information',
          :name      => 'provider-information',
          :condition => {
            :when       => 'cloud_tenant',
            :isNotEmpty => true,
          },
          :fields    => [
            {
              :component   => 'select',
              :id          => 'provider_network_type',
              :name        => 'provider_network_type',
              :label       => _('Provider Network Type'),
              :placeholder => _('Nothing selected'),
              :options     => [
                {:label => _('None')},
                {:label => _('Local'),  :value => 'local'},
                {:label => _('Flat'),   :value => 'flat'},
                {:label => _('GRE'),    :value => 'gre'},
                {:label => _('GENEVE'), :value => 'geneve'},
                {:label => _('VLAN'),   :value => 'vlan'},
                {:label => _('VXLAN'),  :value => 'vxlan'}
              ],
            },
            {
              :component => 'sub-form',
              :id        => 'subform-1',
              :name      => 'subform-1',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'flat',
              },
              :fields    => [
                {
                  :component       => 'text-field',
                  :label           => _('Physical Network'),
                  :maxLength       => 128,
                  :id              => 'provider_physical_network',
                  :name            => 'provider_physical_network',
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                },
              ],
            },
            {
              :component => 'sub-form',
              :id        => 'subform-2',
              :name      => 'subform-2',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'gre',
              },
              :fields    => [
                {
                  :component       => 'text-field',
                  :label           => _('Segmentation ID'),
                  :maxLength       => 128,
                  :id              => 'provider_segmentation_id',
                  :name            => 'provider_segmentation_id',
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                }
              ]
            },
            {
              :component => 'sub-form',
              :id        => 'subform-3',
              :name      => 'subform-3',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'vlan',
              },
              :fields    => [
                {
                  :component       => 'text-field',
                  :label           => _('Physical Network'),
                  :maxLength       => 128,
                  :id              => 'provider_physical_network',
                  :name            => 'provider_physical_network',
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                },
                {
                  :component       => 'text-field',
                  :label           => _('Segmentation ID'),
                  :maxLength       => 128,
                  :id              => 'provider_segmentation_id',
                  :name            => 'provider_segmentation_id',
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                }
              ]
            },
            {
              :component => 'sub-form',
              :id        => 'subform-4',
              :name      => 'subform-4',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'vxlan',
              },
              :fields    => [
                {
                  :component      => 'text-field',
                  :label          => _('Segmentation ID'),
                  :maxLength      => 128,
                  :id             => 'provider_segmentation_id',
                  :name           => 'provider_segmentation_id',
                  :clearOnUnmount => true,
                }
              ]
            }
          ],
        },
        {
          :component => 'sub-form',
          :title     => _('Network Information'),
          :id        => 'network-information',
          :name      => 'network-information',
          :fields    => [
            {
              :component       => 'text-field',
              :id              => 'name',
              :name            => 'name',
              :validateOnMount => true,
              :label           => _('Network Name'),
              :validate        => [
                {
                  :type    => 'required',
                  :message => _('Required'),
                }
              ]
            }
          ]
        },
        {
          :component => 'switch',
          :id        => 'cloud_network_external_facing',
          :name      => 'external_facing',
          :label     => _('External Router'),
          :onText    => _('Yes'),
          :offText   => _('No'),
        },
        {
          :component => 'switch',
          :id        => 'cloud_network_enabled',
          :name      => 'enabled',
          :label     => _('Adminstrative State'),
          :onText    => _('Up'),
          :offText   => _('Down'),
        },
        {
          :component => 'switch',
          :id        => 'cloud_network_shared',
          :name      => 'shared',
          :label     => _('Shared'),
          :onText    => _('Yes'),
          :offText   => _('No'),
        },
      ]
    }
  end

  def params_for_update
    {
      :fields => [
        {
          :component => 'sub-form',
          :title     => _('Placement'),
          :id        => 'placement',
          :name      => 'placement',
          :fields    => [
            {
              :component       => 'select',
              :id              => 'cloud_tenant',
              :name            => 'cloud_tenant',
              :key             => "id-#{ems_id}",
              :label           => _('Cloud Tenant'),
              :placeholder     => "<#{_('Choose')}>",
              :validateOnMount => true,
              :validate        => [{
                :type    => 'required',
                :message => _('Required'),
              }],
              :isDisabled      => !!id,
              :options         => ext_management_system.cloud_tenants.map do |ct|
                {
                  :label => ct.name,
                  :value => ct.id.to_s,
                }
              end,
              :includeEmpty    => true,
              :clearOnUnmount  => true,
            },
          ]
        },
        {
          :component => 'sub-form',
          :title     => _('Network Provider Information'),
          :id        => 'provider-information',
          :name      => 'provider-information',
          :condition => {
            :when       => 'cloud_tenant',
            :isNotEmpty => true,
          },
          :fields    => [
            {
              :component   => 'select',
              :id          => 'provider_network_type',
              :name        => 'provider_network_type',
              :label       => _('Provider Network Type'),
              :placeholder => _('Nothing selected'),
              :options     => [
                {:label => _('None')},
                {:label => _('Local'),  :value => 'local'},
                {:label => _('Flat'),   :value => 'flat'},
                {:label => _('GRE'),    :value => 'gre'},
                {:label => _('GENEVE'), :value => 'geneve'},
                {:label => _('VLAN'),   :value => 'vlan'},
                {:label => _('VXLAN'),  :value => 'vxlan'}
              ],
              :isDisabled  => !!id,
            },
            {
              :component => 'sub-form',
              :id        => 'subform-1',
              :name      => 'subform-1',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'flat',
              },
              :fields    => [
                {
                  :component       => 'text-field',
                  :label           => _('Physical Network'),
                  :maxLength       => 128,
                  :id              => 'provider_physical_network',
                  :name            => 'provider_physical_network',
                  :isDisabled      => !!id,
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                },
              ],
            },
            {
              :component => 'sub-form',
              :id        => 'subform-2',
              :name      => 'subform-2',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'gre',
              },
              :fields    => [
                {
                  :component       => 'text-field',
                  :label           => _('Segmentation ID'),
                  :maxLength       => 128,
                  :id              => 'provider_segmentation_id',
                  :name            => 'provider_segmentation_id',
                  :isDisabled      => !!id,
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                }
              ]
            },
            {
              :component => 'sub-form',
              :id        => 'subform-3',
              :name      => 'subform-3',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'vlan',
              },
              :fields    => [
                {
                  :component       => 'text-field',
                  :label           => _('Physical Network'),
                  :maxLength       => 128,
                  :id              => 'provider_physical_network',
                  :name            => 'provider_physical_network',
                  :isDisabled      => !!id,
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                },
                {
                  :component       => 'text-field',
                  :label           => _('Segmentation ID'),
                  :maxLength       => 128,
                  :id              => 'provider_segmentation_id',
                  :name            => 'provider_segmentation_id',
                  :isDisabled      => !!id,
                  :validateOnMount => true,
                  :clearOnUnmount  => true,
                  :validate        => [{
                    :type    => 'required',
                    :message => _('Required'),
                  }],
                }
              ]
            },
            {
              :component => 'sub-form',
              :id        => 'subform-4',
              :name      => 'subform-4',
              :condition => {
                :when => 'provider_network_type',
                :is   => 'vxlan',
              },
              :fields    => [
                {
                  :component      => 'text-field',
                  :label          => _('Segmentation ID'),
                  :maxLength      => 128,
                  :id             => 'provider_segmentation_id',
                  :name           => 'provider_segmentation_id',
                  :isDisabled     => !!id,
                  :clearOnUnmount => true,
                }
              ]
            }
          ],
        },
        {
          :component => 'sub-form',
          :title     => _('Network Information'),
          :id        => 'network-information',
          :name      => 'network-information',
          :fields    => [
            {
              :component       => 'text-field',
              :id              => 'name',
              :name            => 'name',
              :validateOnMount => true,
              :label           => _('Network Name'),
              :validate        => [
                {
                  :type    => 'required',
                  :message => _('Required'),
                }
              ]
            }
          ]
        },
        {
          :component => 'switch',
          :id        => 'cloud_network_external_facing',
          :name      => 'external_facing',
          :label     => _('External Router'),
          :onText    => _('Yes'),
          :offText   => _('No'),
        },
        {
          :component => 'switch',
          :id        => 'cloud_network_enabled',
          :name      => 'enabled',
          :label     => _('Adminstrative State'),
          :onText    => _('Up'),
          :offText   => _('Down'),
        },
        {
          :component => 'switch',
          :id        => 'cloud_network_shared',
          :name      => 'shared',
          :label     => _('Shared'),
          :onText    => _('Yes'),
          :offText   => _('No'),
        },
      ]
    }
  end

  def self.class_by_ems(ext_management_system, external = false)
    external ? super::Public : super::Private
  end

  def self.remapping(options)
    new_options = options.dup
    new_options[:router_external] = options[:external_facing] if options[:external_facing]
    new_options.delete(:external_facing)
    new_options
  end

  def self.raw_create_cloud_network(ext_management_system, options)
    cloud_tenant = options.delete(:cloud_tenant)
    network = nil
    raw_options = remapping(options)
    ext_management_system.with_provider_connection(connection_options(cloud_tenant)) do |service|
      network = service.networks.new(raw_options)
      network.save
    end
    {:ems_ref => network.id, :name => options[:name]}
  rescue => e
    _log.error "network=[#{options[:name]}], error: #{e}"
    parsed_error = parse_error_message_from_neutron_response(e)
    raise MiqException::MiqNetworkCreateError, parsed_error, e.backtrace
  end

  def raw_delete_cloud_network(_options = {})
    with_notification(:cloud_network_delete,
                      :options => {
                        :subject => self,
                      }) do
      ext_management_system.with_provider_connection(connection_options(cloud_tenant)) do |service|
        service.delete_network(ems_ref)
      end
    end
  rescue => e
    _log.error "network=[#{name}], error: #{e}"
    raise MiqException::MiqNetworkDeleteError, parse_error_message_from_neutron_response(e), e.backtrace
  end

  def raw_update_cloud_network(options)
    ext_management_system.with_provider_connection(connection_options(cloud_tenant)) do |service|
      service.update_network(ems_ref, options)
    end
  rescue => e
    _log.error "network=[#{name}], error: #{e}"
    raise MiqException::MiqNetworkUpdateError, parse_error_message_from_neutron_response(e), e.backtrace
  end

  def self.connection_options(cloud_tenant = nil)
    connection_options = {:service => "Network"}
    connection_options[:tenant_name] = cloud_tenant.name if cloud_tenant
    connection_options
  end

  def ip_address_total_count
    # TODO(lsmola) Rather storing this in DB? It should be changing only in refresh
    @ip_address_total_count ||= cloud_subnets.all.sum do |subnet|
      # We substract 1 because the first address of the pool is always reserved. For private network it is for DHCP, for
      # public network it's a port for Router.
      subnet.allocation_pools.sum { |x| (IPAddr.new(x["start"])..IPAddr.new(x["end"])).map(&:to_s).count - 1 }
    end
  end

  def ip_address_left_count(reload = false)
    @ip_address_left_count = nil if reload
    @ip_address_left_count ||= ip_address_total_count - ip_address_used_count(reload)
  end

  def ip_address_left_count_live(reload = false)
    @ip_address_left_count_live = nil if reload
    # Live method is asking API drectly for current count of consumed addresses
    @ip_address_left_count_live ||= ip_address_total_count - ip_address_used_count_live(reload)
  end

  def ip_address_used_count(reload = false)
    @ip_address_used_count = nil if reload
    if @public
      # Number of all floating Ips, since we are doing association by creating FloatingIP, because
      # associate is not atomic.
      @ip_address_used_count ||= floating_ips.count
    else
      @ip_address_used_count ||= vms.count
    end
  end

  def ip_address_used_count_live(reload = false)
    @ip_address_used_count_live = nil if reload
    if @public
      # Number of ports with fixed IPs plugged into the network. Live means it talks directly to OpenStack API
      # TODO(lsmola) we probably need paginated API call, there should be no multitenancy needed, but the current
      # UI code allows to mix tenants, so it could be needed, athough netron doesn seem to have --all-tenants calls,
      # so when I use admin, I can see other tenant resources. Investigate, fix.
      @ip_address_used_count_live ||= ext_management_system.with_provider_connection(
        :service     => "Network",
        :tenant_name => cloud_tenant.name
      ) do |connection|
        connection.floating_ips.all(:floating_network_id => ems_ref).count
      end
    else
      @ip_address_used_count_live ||= ext_management_system.with_provider_connection(
        :service     => "Network",
        :tenant_name => cloud_tenant.name
      ) do |connection|
        connection.ports.all(:network_id => ems_ref, :device_owner => "compute:None").count
      end
    end
  end

  def ip_address_utilization(reload = false)
    @ip_address_utilization = nil if reload
    # If total count is 0, utilization should be 100
    @ip_address_utilization ||= begin
      ip_address_total_count > 0 ? (100.0 / ip_address_total_count) * ip_address_used_count(reload) : 100
    end
  end

  def ip_address_utilization_live(reload = false)
    @ip_address_utilization_live = nil if reload
    # Live method is asking API drectly for current count of consumed addresses
    # If total count is 0, utilization should be 100
    @ip_address_utilization_live ||= begin
      ip_address_total_count > 0 ? (100.0 / ip_address_total_count) * ip_address_used_count_live(reload) : 100
    end
  end

  def self.display_name(number = 1)
    n_('Cloud Network (OpenStack)', 'Cloud Networks (OpenStack)', number)
  end

  private

  def connection_options(cloud_tenant = nil)
    self.class.connection_options(cloud_tenant)
  end
end