ManageIQ/manageiq-providers-openstack

View on GitHub
app/models/manageiq/providers/openstack/infra_manager.rb

Summary

Maintainability
A
3 hrs
Test Coverage
C
70%
class ManageIQ::Providers::Openstack::InfraManager < ManageIQ::Providers::InfraManager
  include ManageIQ::Providers::Openstack::ManagerMixin
  include HasManyOrchestrationStackMixin
  include HasNetworkManagerMixin

  before_save :ensure_parent_provider
  before_destroy :destroy_parent_provider
  before_create :ensure_managers
  before_update :ensure_managers_zone_and_provider_region

  supports :create
  supports :catalog
  supports :metrics
  supports :events do
    _("Events are not supported") unless capabilities["events"]
  end
  supports_not :shutdown

  def self.params_for_create
    {
      :fields => [
        {
          :component    => "select",
          :id           => "api_version",
          :name         => "api_version",
          :label        => _("API Version"),
          :initialValue => 'v3',
          :isRequired   => true,
          :validate     => [{:type => "required"}],
          :options      => [
            {
              :label => 'Keystone V2',
              :value => 'v2',
            },
            {
              :label => 'Keystone V3',
              :value => 'v3',
            },
          ],
        },
        {
          :component  => 'text-field',
          :id         => 'uid_ems',
          :name       => 'uid_ems',
          :label      => _('Domain ID'),
          :isRequired => true,
          :condition  => {
            :when => 'api_version',
            :is   => 'v3',
          },
          :validate   => [{
            :type      => "required",
            :condition => {
              :when => 'api_version',
              :is   => 'v3',
            }
          }],
        },
        {
          :component => 'sub-form',
          :id        => 'endpoints-subform',
          :name      => 'endpoints-subform',
          :title     => _('Endpoints'),
          :fields    => [
            :component => 'tabs',
            :name      => 'tabs',
            :fields    => [
              {
                :component => 'tab-item',
                :id        => 'default-tab',
                :name      => 'default-tab',
                :title     => _('Default'),
                :fields    => [
                  {
                    :component              => 'validate-provider-credentials',
                    :id                     => 'authentications.default.valid',
                    :name                   => 'authentications.default.valid',
                    :skipSubmit             => true,
                    :isRequired             => true,
                    :validationDependencies => %w[name type zone_id api_version provider_region keystone_v3_domain_id],
                    :fields                 => [
                      {
                        :component    => "select",
                        :id           => "endpoints.default.security_protocol",
                        :name         => "endpoints.default.security_protocol",
                        :label        => _("Security Protocol"),
                        :isRequired   => true,
                        :initialValue => 'ssl-with-validation',
                        :validate     => [{:type => "required"}],
                        :options      => [
                          {
                            :label => _("SSL without validation"),
                            :value => "ssl-no-validation"
                          },
                          {
                            :label => _("SSL"),
                            :value => "ssl-with-validation"
                          },
                          {
                            :label => _("Non-SSL"),
                            :value => "non-ssl"
                          }
                        ]
                      },
                      {
                        :component  => "text-field",
                        :id         => "endpoints.default.hostname",
                        :name       => "endpoints.default.hostname",
                        :label      => _("Hostname (or IPv4 or IPv6 address)"),
                        :isRequired => true,
                        :validate   => [{:type => "required"}],
                      },
                      {
                        :component    => "text-field",
                        :id           => "endpoints.default.port",
                        :name         => "endpoints.default.port",
                        :label        => _("API Port"),
                        :type         => "number",
                        :initialValue => 13_000,
                        :isRequired   => true,
                        :validate     => [{:type => "required"}],
                      },
                      {
                        :component  => "text-field",
                        :id         => "authentications.default.userid",
                        :name       => "authentications.default.userid",
                        :label      => _("Username"),
                        :isRequired => true,
                        :validate   => [{:type => "required"}],
                      },
                      {
                        :component  => "password-field",
                        :id         => "authentications.default.password",
                        :name       => "authentications.default.password",
                        :label      => _("Password"),
                        :type       => "password",
                        :isRequired => true,
                        :validate   => [{:type => "required"}],
                      },
                    ]
                  },
                ]
              },
              {
                :component => 'tab-item',
                :id        => 'events-tab',
                :name      => 'events-tab',
                :title     => _('Events'),
                :fields    => [
                  {
                    :component    => 'protocol-selector',
                    :id           => 'event_stream_selection',
                    :name         => 'event_stream_selection',
                    :skipSubmit   => true,
                    :initialValue => 'ceilometer',
                    :label        => _('Type'),
                    :options      => [
                      {
                        :label => _('Ceilometer'),
                        :value => 'ceilometer',
                      },
                      {
                        :label => _('AMQP'),
                        :value => 'amqp',
                        :pivot => 'endpoints.amqp.hostname',
                      },
                    ],
                  },
                  {
                    :component    => 'text-field',
                    :type         => 'hidden',
                    :id           => 'endpoints.ceilometer',
                    :name         => 'endpoints.ceilometer',
                    :initialValue => {},
                    :condition    => {
                      :when => 'event_stream_selection',
                      :is   => 'ceilometer',
                    },
                  },
                  {
                    :component              => 'validate-provider-credentials',
                    :id                     => 'endpoints.amqp.valid',
                    :name                   => 'endpoints.amqp.valid',
                    :skipSubmit             => true,
                    :isRequired             => true,
                    :validationDependencies => %w[type zone_id event_stream_selection],
                    :condition              => {
                      :when => 'event_stream_selection',
                      :is   => 'amqp',
                    },
                    :fields                 => [
                      {
                        :component  => "text-field",
                        :id         => "endpoints.amqp.hostname",
                        :name       => "endpoints.amqp.hostname",
                        :label      => _("Hostname (or IPv4 or IPv6 address)"),
                        :isRequired => true,
                        :validate   => [{:type => "required"}],
                      },
                      {
                        :component    => "text-field",
                        :id           => "endpoints.amqp.port",
                        :name         => "endpoints.amqp.port",
                        :label        => _("API Port"),
                        :type         => "number",
                        :isRequired   => true,
                        :initialValue => 5672,
                        :validate     => [{:type => "required"}],
                      },
                      {
                        :component  => "text-field",
                        :id         => "authentications.amqp.userid",
                        :name       => "authentications.amqp.userid",
                        :label      => _("Username"),
                        :isRequired => true,
                        :validate   => [{:type => "required"}],
                      },
                      {
                        :component  => "password-field",
                        :id         => "authentications.amqp.password",
                        :name       => "authentications.amqp.password",
                        :label      => _("Password"),
                        :type       => "password",
                        :isRequired => true,
                        :validate   => [{:type => "required"}],
                      },
                    ],
                  },
                ],
              },
              {
                :component => 'tab-item',
                :id        => 'ssh_keypair-tab',
                :name      => 'ssh_keypair-tab',
                :title     => _('RSA key pair'),
                :fields    => [
                  :component => 'provider-credentials',
                  :id        => 'endpoints.ssh_keypair.valid',
                  :name      => 'endpoints.ssh_keypair.valid',
                  :fields    => [
                    {
                      :component    => 'text-field',
                      :type         => 'hidden',
                      :id           => 'endpoints.ssh_keypair',
                      :name         => 'endpoints.ssh_keypair',
                      :initialValue => {},
                      :condition    => {
                        :when       => 'authentications.ssh_keypair.userid',
                        :isNotEmpty => true,
                      },
                    },
                    {
                      :component => "text-field",
                      :id        => "authentications.ssh_keypair.userid",
                      :name      => "authentications.ssh_keypair.userid",
                      :label     => _("Username"),
                    },
                    {
                      :component      => "password-field",
                      :id             => "authentications.ssh_keypair.auth_key",
                      :name           => "authentications.ssh_keypair.auth_key",
                      :componentClass => 'textarea',
                      :rows           => 10,
                      :label          => _("Private Key"),
                    },
                  ],
                ],
              },
            ],
          ],
        },
      ]
    }
  end

  def ensure_network_manager
    build_network_manager(:type => 'ManageIQ::Providers::Openstack::NetworkManager') unless network_manager
  end

  def allow_targeted_refresh?
    false
  end

  # A placeholder relation for NetworkTopology to work
  def availability_zones
  end

  def cloud_tenants
    self.class.none
  end

  def host_aggregates
    HostAggregate.where(:ems_id => provider.try(:cloud_ems).try(:collect, &:id).try(:uniq))
  end

  def ensure_parent_provider
    # TODO(lsmola) this might move to a general management of Providers, but for now, we will ensure, every
    # EmsOpenstackInfra has associated a Provider. This relation will serve for relating EmsOpenstackInfra
    # to possible many EmsOpenstacks deployed through EmsOpenstackInfra

    # Name of the provider needs to be unique, get provider if there is one like that
    self.provider = ManageIQ::Providers::Openstack::Provider.find_by(:name => name) unless provider

    attributes = {:name => name, :zone => zone}
    if provider
      provider.update!(attributes)
    else
      self.provider = ManageIQ::Providers::Openstack::Provider.create!(attributes)
    end
  end

  def destroy_parent_provider
    provider.try(:destroy)
  end

  def self.ems_type
    @ems_type ||= "openstack_infra".freeze
  end

  def self.description
    @description ||= "OpenStack Platform Director".freeze
  end

  def self.default_blacklisted_event_names
    %w(
      identity.authenticate
    )
  end

  def supported_auth_types
    %w(default amqp ssh_keypair)
  end

  def supported_auth_attributes
    %w(userid password auth_key)
  end

  def self.catalog_types
    {"openstack" => N_("OpenStack")}
  end

  def self.event_monitor_class
    ManageIQ::Providers::Openstack::InfraManager::EventCatcher
  end

  def verify_credentials(auth_type = nil, options = {})
    options[:service] ||= "Baremetal"

    super
  end

  def required_credential_fields(type)
    case type.to_s
    when 'ssh_keypair' then [:userid, :auth_key]
    else                    [:userid, :password]
    end
  end

  def verify_ssh_keypair_credentials(_options)
    # Select one powered-on host in each cluster to verify
    # ssh credentials against
    hosts.select(&:ems_cluster_id)
         .sort_by(&:ems_cluster_id)
         .slice_when { |i, j| i.ems_cluster_id != j.ems_cluster_id }
         .map { |c| c.find { |h| h.power_state == 'on' } }.compact
         .all? { |h| h.verify_credentials('ssh_keypair') }
  end
  private :verify_ssh_keypair_credentials

  def workflow_service
    openstack_handle.detect_workflow_service
  end

  def register_and_configure_nodes(nodes_json)
    connection = openstack_handle.detect_workflow_service
    workflow = "tripleo.baremetal.v1.register_or_update"
    input = { :nodes_json => nodes_json }
    response = connection.create_execution(workflow, input)
    state = response.body["state"]
    workflow_execution_id = response.body["id"]

    while state == "RUNNING"
      sleep 5
      response = connection.get_execution(workflow_execution_id)
      state = response.body["state"]
    end

    EmsRefresh.queue_refresh(@infra) if state == "SUCCESS"

    # Configures boot image for all manageable nodes.
    # It would be preferred to only configure the nodes that were just added, but
    # we don't know the uuids from the response. The uuids are available in Zaqar.
    # Once we add support for reading Zaqar, we can change this to be more
    # selective.
    connection.create_execution("tripleo.baremetal.v1.configure_manageable_nodes")

    [state, response.body.to_s]
  end

  def self.display_name(number = 1)
    n_('Infrastructure Provider (OpenStack)', 'Infrastructure Providers (OpenStack)', number)
  end

  private

  def authentication_class(attributes)
    attributes.symbolize_keys[:auth_key] ? ManageIQ::Providers::Openstack::InfraManager::AuthKeyPair : super
  end
end