Spec-Zone.ru › Chef 16

Создание пользовательских плагинов Ohai

[править на GitHub]

Пользовательские плагины Ohai описывают дополнительные атрибуты конфигурации, которые должны собираться Ohai и предоставляться клиенту Chef Infra во время выполнения.

Плагины Ohai написаны на Ruby с использованием DSL-плагина, документация которого приведена ниже. Написание на Ruby предоставляет доступ ко всем встроенным функциям Ruby, а также к функциональности сторонних библиотек (gem). Плагины могут парсить вывод любых локальных команд на узле или получать данные из внешних API. Вот примеры плагинов, написанных пользователями: - Плагин для сбора информации об узле, включая дата-центр, стойку и позицию стойки из сервера инвентаризации - Плагин для сбора дополнительной информации о массивах RAID из утилиты контроллера - Плагин для сбора информации о гарантии оборудования из API поставщика

См. О плагине Ohai для получения информации о конфигурации и использовании Ohai.

Синтаксис

Синтаксис плагина Ohai следующий:

Ohai.plugin(:Name) do
  provides 'attribute', 'attribute/subattribute'
  depends 'attribute', 'attribute'

  def shared_method
    # some Ruby code that defines the shared method
    attribute my_data
  end

  collect_data(:default) do
    # some Ruby code
    attribute my_data
  end

  collect_data(:platform...) do
    # some Ruby code that defines platform-specific requirements
    attribute my_data
  end
end

где

  • Обязательно. (:Name) используется для идентификации плагина; если у двух плагинов одинаковое (:Name), эти плагины объединяются и выполняются как один. Это значение должно быть корректным именем класса Ruby, начинающимся с заглавной буквы и содержащим только буквенно-цифровые символы
  • Обязательно. provides — это список атрибутов, определённых данным плагином, разделённых запятыми. Этот атрибут станет автоматическим атрибутом (т.е. node['attribute']) после сбора Ohai в начале выполнения Chef Infra Client. Атрибут также может быть определён с использованием шаблона attribute/subattribute
  • depends — это список атрибутов, собранных другим плагином, разделённых запятыми. До тех пор, пока значение собирается другим плагином Ohai, им может воспользоваться любой плагин.
  • shared_method определяет код, который может быть использован несколькими блоками collect_data; например, вместо определения mash для каждого блока collect_data, код может быть определён как общий метод, а затем вызван из любого блока collect_data
  • collect_data — это блок кода Ruby, который вызывается Ohai при выполнении; в плагине можно определить один или несколько блоков collect_data, но выполняется только один блок collect_data
  • collect_data(:default) — это блок кода, который выполняется, когда платформа узла не определена блоком collect_data, специфичным для платформы.
  • collect_data(:platform) — это блок кода, специфичный для платформы, который выполняется, когда существует соответствие между платформой узла и этим блоком collect_data; для каждой платформы может существовать только один блок collect_data; возможные значения: :aix, :darwin, :freebsd, :linux, :openbsd, :netbsd, :solaris2, :windows, или любое другое значение из RbConfig::CONFIG['host_os']
  • my_data — это строка (a string value) или пустой mash ({ :setting_a => 'value_a', :setting_b => 'value_b' }). Это используется для определения данных, которые должен собрать плагин.

Например, следующий плагин ищет данные о виртуальных машинах, размещённых в Amazon EC2, Google Compute Engine, Rackspace, Eucalyptus, Linode, OpenStack и Microsoft Azure:

Ohai.plugin(:Cloud) do
  provides 'cloud'

  depends 'ec2'
  depends 'gce'
  depends 'rackspace'
  depends 'eucalyptus'
  depends 'linode'
  depends 'openstack'
  depends 'azure'

  def create_objects
    cloud Mash.new
    cloud[:public_ips] = []
    cloud[:private_ips] = []
  end

  ...

  def on_gce?
    gce != nil
  end

  def get_gce_values
    cloud[:public_ipv4] = []
    cloud[:local_ipv4] = []

    public_ips = gce['instance']['networkInterfaces'].collect do |interface|
      if interface.has_key?('accessConfigs')
        interface['accessConfigs'].collect{|ac| ac['externalIp']}
      end
    end.flatten.compact

    private_ips = gce['instance']['networkInterfaces'].collect do |interface|
      interface['ip']
    end.compact

    cloud[:public_ips] += public_ips
    cloud[:private_ips] += private_ips
    cloud[:public_ipv4] +=  public_ips
    cloud[:public_hostname] = nil
    cloud[:local_ipv4] += private_ips
    cloud[:local_hostname] = gce['instance']['hostname']
    cloud[:provider] = 'gce'
  end

  ...

  # with following similar code blocks for each cloud provider

где

  • provides определяет атрибут cloud, который затем преобразуется в объект с помощью общего метода create_objects, который генерирует хеш на основе общедоступных или частных IP-адресов.
  • Если поставщик облачных сервисов — Google Compute Engine, то данные атрибута cloud заполняются в хеш на основе IP-адреса узла.

Чтобы увидеть остальную часть кода в этом плагине, перейдите по адресу: https://github.com/chef/ohai/blob/master/lib/ohai/plugins/cloud.rb.

Методы Ohai DSL

Ohai DSL — это Ruby DSL, который используется для определения плагина Ohai и для обеспечения сбора правильных данных Ohai в начале каждого выполнения Chef Infra Client. Ohai DSL — это небольшой DSL с одним методом, специфичным для плагинов Ohai. Поскольку Ohai DSL — это Ruby DSL, всё, что можно сделать с помощью Ruby, также можно сделать при определении плагина Ohai.

collect_data

Метод collect_data — это блок кода Ruby, который вызывается Ohai при выполнении. В плагине можно определить один или несколько блоков collect_data, но выполняется только один блок collect_data. Блок collect_data, который выполняется, определяется платформой, на которой работает узел, которая затем сопоставляется с доступными блоками collect_data в плагине.

  • Блок collect_data(:default) используется, когда Ohai не может сопоставить платформу узла с блоком collect_data(:platform), специфичным для платформы, в плагине.
  • Блок collect_data(:platform) требуется для каждой платформы, требующей отличного от стандартного поведения.

Если при выполнении Ohai нет соответствующего блока collect_data для платформы, используется блок collect_data(:default). Синтаксис метода collect_data:

collect_data(:default) do
  # some Ruby code
end

или:

collect_data(:platform) do
  # some Ruby code
end

где:

  • :default — имя стандартного блока collect_data
  • :platform — имя платформы, например, :aix для AIX или :windows для Microsoft Windows

Использование Mash

Для хранения данных используйте mash. Это делается путём создания нового mash и установки в него атрибута. Например:

provides 'name_of_mash'
name_of_mash Mash.new
name_of_mash[:attribute] = 'value'

Примеры

Следующие примеры демонстрируют использование блока collect_data

Ohai.plugin(:Azure) do
  provides 'azure'

  collect_data do
    azure_metadata_from_hints = hint?('azure')
    if azure_metadata_from_hints
      Ohai::Log.debug('azure_metadata_from_hints is present.')
      azure Mash.new
      azure_metadata_from_hints.each {|k, v| azure[k] = v }
    else
      Ohai::Log.debug('No hints present for azure.')
      false
    end
  end
end

или:

require 'ohai/mixin/ec2_metadata'
extend Ohai::Mixin::Ec2Metadata

Ohai.plugin do
  provides 'openstack'

  collect_data do
    if hint?('openstack') || hint?('hp')
      Ohai::Log.debug('ohai openstack')
      openstack Mash.new
      if can_metadata_connect?(EC2_METADATA_ADDR,80)
        Ohai::Log.debug('connecting to the OpenStack metadata service')
        self.fetch_metadata.each {|k, v| openstack[k] = v }
        case
        when hint?('hp')
          openstack['provider'] = 'hp'
        else
          openstack['provider'] = 'openstack'
        end
      else
        Ohai::Log.debug('unable to connect to the OpenStack metadata service')
      end
    else
      Ohai::Log.debug('NOT ohai openstack')
    end
  end
end

require

Метод require — стандартный метод Ruby, который может использоваться для перечисления файлов, которые могут потребоваться платформой, например, внешней библиотекой классов. В качестве лучшей практики, даже если метод require часто используется в начале файла Ruby, рекомендуется использовать метод require как часть блока collect_data, специфичного для платформы. Например, Ruby WMI необходим для Microsoft Windows:

collect_data(:windows) do
  require 'ruby-wmi'
  WIN32OLE.codepage = WIN32OLE::CP_UTF8

  kernel Mash.new

  host = WMI::Win32_OperatingSystem.find(:first)
  kernel[:os_info] = Mash.new
  host.properties_.each do |p|
    kernel[:os_info][p.name.wmi_underscore.to_sym] = host.send(p.name)
  end

  ...

end

Ohai попытается полностью квалифицировать имя любого класса, добавив Ohai:: к загруженному классу. Например, оба:

require Ohai::Mixin::ShellOut

и:

require Mixin::ShellOut

понимаются Ohai одинаково: Ohai::Mixin::ShellOut

Если класс является внешним (и, следовательно, к нему не должно добавляться Ohai::), используйте ::, чтобы сообщить Ohai об этом. Например:

::External::Class::Library

/common Directory

Директория /common хранит код, используемый во всех плагинах Ohai. Например, файл в директории /common с именем virtualization.rb , содержащий код, подобный следующему:

module Ohai
  module Common
    module Virtualization

      def host?(virtualization)
        !virtualization.nil? && virtualization[:role].eql?('host')
      end

      def open_virtconn(system)
        begin
          require 'libvirt'
          require 'hpricot'
        rescue LoadError => e
          Ohai::Log.debug('Cannot load gem: #{e}.')
        end

        emu = (system.eql?('kvm') ? 'qemu' : system)
        virtconn = Libvirt::open_read_only('#{emu}:///system')
      end

      ...

      def networks(virtconn)
        networks = Mash.new
        virtconn.list_networks.each do |n|
          nv = virtconn.lookup_network_by_name n
          networks[n] = Mash.new
          networks[n][:xml_desc] = (nv.xml_desc.split('\n').collect {|line| line.strip}).join
          ['bridge_name','uuid'].each {|a| networks[n][a] = nv.send(a)}
          #xdoc = Hpricot networks[n][:xml_desc]
        end
        networks
      end

      ...

    end
  end
end

может быть использован в плагине с использованием метода require для загрузки файла virtualization.rb и последующего вызова каждого метода в загруженном модуле:

require 'ohai/common/virtualization'

Ohai.plugin(:Virtualization) do
  include Ohai::Common::Virtualization

  provides 'virtualization'
  %w{ capabilities domains networks storage }.each do |subattr|
    provides 'virtualization/#{subattr}'
  end

  collect_data(:linux) do
    virtualization Mash.new

    ...

    if host?(virtualization)
      v = open_virtconn(virtualization[:system])

      virtualization[:libvirt_version] = libvirt_version(v)
      virtualization[:nodeinfo] = nodeinfo(v)
      virtualization[:uri] = uri(v)
      virtualization[:capabilities] = capabilities(v)
      virtualization[:domains] = domains(v)
      virtualization[:networks] = networks(v)
      virtualization[:storage] = storage(v)

      close_virtconn(v)
    end

Общие методы

Общий метод определяет поведение, которое может быть использовано более чем одним блоком collect_data, таким как структура данных, хеш или mash. Синтаксис общего метода:

def a_shared_method
  # some Ruby code that defines the shared method
end

Например, следующий общий метод используется для сбора данных о различных поставщиках облачных сервисов в зависимости от поставщика облачных сервисов и типа IP-адреса:

def create_objects
  cloud Mash.new
  cloud[:public_ips] = Array.new
  cloud[:private_ips] = Array.new
end

а затем позже в том же плагине объект cloud может быть повторно использован:

def get_linode_values
  cloud[:public_ips] << linode['public_ip']
  cloud[:private_ips] << linode['private_ip']
  cloud[:public_ipv4] = linode['public_ipv4']
  cloud[:public_hostname] = linode['public_hostname']
  cloud[:local_ipv4] = linode['local_ipv4']
  cloud[:local_hostname] = linode['local_hostname']
  cloud[:provider] = 'linode'
end

и

def get_azure_values
  cloud[:vm_name] = azure['vm_name']
  cloud[:public_ips] << azure['public_ip']
  cloud[:public_fqdn] = azure['public_fqdn']
  cloud[:public_ssh_port] = azure['public_ssh_port'] if azure['public_ssh_port']
  cloud[:public_winrm_port] = azure['public_winrm_port'] if azure['public_winrm_port']
  cloud[:provider] = 'azure'
end

и так далее для каждого поставщика облачных сервисов.

Ведение журнала

Используйте класс Ohai::Log в плагине Ohai для определения записей журнала, созданных Ohai. Синтаксис сообщения журнала:

Ohai::Log.log_type('message')

где

  • log_type может быть .debug, .info, .warn, .error, или .fatal
  • 'message' — это сообщение, которое записывается в журнал.

Например:

Ohai.plugin do
  provides 'openstack'

  collect_data do
    if hint?('openstack') || hint?('hp')
      Ohai::Log.debug('ohai openstack')
      openstack Mash.new
      if can_metadata_connect?(EC2_METADATA_ADDR,80)
        Ohai::Log.debug('connecting to the OpenStack metadata service')
        self.fetch_metadata.each {|k, v| openstack[k] = v }
        case
        when hint?('hp')
          openstack['provider'] = 'hp'
        else
          openstack['provider'] = 'openstack'
        end
      else
        Ohai::Log.debug('unable to connect to the OpenStack metadata service')
      end
    else
      Ohai::Log.debug('NOT ohai openstack')
    end
  end
end

rescue

Используйте инструкцию rescue для обеспечения того, что сообщение журнала всегда предоставляется. Например:

rescue LoadError => e
  Ohai::Log.debug('ip_scopes: cannot load gem, plugin disabled: #{e}')
end

Примеры

Примечание

См. https://github.com/rackerlabs/ohai-plugins/tree/master/plugins для отличных примеров пользовательских плагинов Ohai.

Следующие примеры демонстрируют различные способы создания плагинов Ohai.

Блоки collect_data

Следующий плагин Ohai использует несколько блоков collect_data и общие методы для определения платформ:

Ohai.plugin(:Hostname) do
  provides 'domain', 'fqdn', 'hostname'

  def from_cmd(cmd)
    so = shell_out(cmd)
    so.stdout.split($/)[0]
  end

  def collect_domain
    if fqdn
      fqdn =~ /.+?\.(.*)/
      domain $1
    end
  end

  collect_data(:aix, :hpux) do
    hostname from_cmd('hostname -s')
    fqdn from_cmd('hostname')
    domain collect_domain
  end

  collect_data(:darwin, :netbsd, :openbsd) do
    hostname from_cmd('hostname -s')
    fqdn from_cmd('hostname')
    domain collect_domain
  end

  collect_data(:freebsd) do
    hostname from_cmd('hostname -s')
    fqdn from_cmd('hostname -f')
    domain collect_domain
  end

  collect_data(:linux) do
    hostname from_cmd('hostname -s')
    begin
      fqdn from_cmd('hostname --fqdn')
    rescue
      Ohai::Log.debug('hostname -f returned an error, probably no domain is set')
    end
    domain collect_domain
  end

  collect_data(:solaris2) do
    require 'socket'

    hostname from_cmd('hostname')

    fqdn_lookup = Socket.getaddrinfo(hostname, nil, nil, nil, nil, Socket::AI_CANONNAME).first[2]
    if fqdn_lookup.split('.').length > 1
      # we received an fqdn
      fqdn fqdn_lookup
    else
      # default to assembling one
      h = from_cmd('hostname')
      d = from_cmd('domainname')
      fqdn '#{h}.#{d}'
    end

    domain collect_domain
  end

  collect_data(:windows) do
    require 'ruby-wmi'
    require 'socket'

    host = WMI::Win32_ComputerSystem.find(:first)
    hostname '#{host.Name}'

    info = Socket.gethostbyname(Socket.gethostname)
    if info.first =~ /.+?\.(.*)/
      fqdn info.first
    else
      # host is not in dns. optionally use:
      # C:\WINDOWS\system32\drivers\etc\hosts
      fqdn Socket.gethostbyaddr(info.last).first
    end

   domain collect_domain
  end
end

Использование библиотеки mixin

Следующий пример плагина Ohai демонстрирует, как плагин может использовать библиотеку mixin и зависеть от другого плагина:

require 'ohai/mixin/os'

Ohai.plugin(:Os) do
  provides 'os', 'os_version'
  depends 'kernel'

  collect_data do
    os collect_os
    os_version kernel[:release]
  end
end

Получение значений ядра

Следующий пример плагина Ohai демонстрирует часть файла, получающего начальные значения атрибутов ядра:

Ohai.plugin(:Kernel) do
  provides 'kernel', 'kernel/modules'

  def init_kernel
    kernel Mash.new
    [['uname -s', :name], ['uname -r', :release],
    ['uname -v', :version], ['uname -m', :machine]].each do |cmd, property|
      so = shell_out(cmd)
      kernel[property] = so.stdout.split($/)[0]
    end
    kernel
  end

  ...

  collect_data(:darwin) do
    kernel init_kernel
    kernel[:os] = kernel[:name]

    so = shell_out('sysctl -n hw.optional.x86_64')
    if so.stdout.split($/)[0].to_i == 1
      kernel[:machine] = 'x86_64'
    end

    modules = Mash.new
    so = shell_out('kextstat -k -l')
    so.stdout.lines do |line|
      if line =~ /(\d+)\s+(\d+)\s+0x[0-9a-f]+\s+0x([0-9a-f]+)\s+0x[0-9a-f]+\s+([a-zA-Z0-9\.]+) \(([0-9\.]+)\)/
        kext[$4] = { :version => $5, :size => $3.hex, :index => $1, :refcount => $2 }
      end
    end

    kernel[:modules] = modules
  end

  ...

© Chef Software, Inc.
Licensed under the Creative Commons Attribution 3.0 Unported License.
The Chef™ Mark and Chef Logo are either registered trademarks/service marks or trademarks/servicemarks of Chef, in the United States and other countries and are used with Chef Inc's permission.
We are not affiliated with, endorsed or sponsored by Chef Inc.
https://docs.chef.io/ohai_custom/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API