puppet-rspec — Как передать параметры вложенным классам профилей во время модульного теста?

Я пытаюсь создать два профиля Puppet для DCS Hashicorp Consul. Consul может работать как клиентский или серверный агент, при этом серверный режим является надмножеством клиентского режима. Это прямо отражено в конфигурации:

Агентам сервера Consul обычно требуется расширенный набор настроек, необходимых для клиентских агентов Consul.

Мой подход к дизайну Puppet основан на следующем шаблоне: https://puppet.com/docs/pe/2018.1/the_roles_and_profiles_method.html

Согласно документации Puppet, должно быть возможно (и, скорее всего, желательно) включить профиль consul_client в профиль consul_server, чтобы избежать дублирования кода:

Профили могут включать в себя другие профили.

Пытаясь реализовать это, я использовал некоторые обязательные параметры в обоих профилях и столкнулся с проблемами во время выполнения автоматических модульных тестов rspec.

В файле модульного теста consul_client consul_client_spec.rb я просто указал необходимые параметры следующим образом:

let(:params) { {
  'datacenter' => 'unit-test',
  'encrypt' => 'DUMMY',
  'server_agent_nodes' => [ '1.2.3.4' ]
} }

Возникли проблемы при попытке запустить модульный тест consul_server_spec.rb. По наивности я просто передал один дополнительный обязательный параметр профиля consul_server:

let(:params) { {
  'bootstrap_expect' => 3,
} }

Поскольку профиль consul_client является includeed/requireed профилем consul_server, тест не прошел с отсутствующими параметрами для класса профиля consul_client. Кажется, это указывает на некоторую общую структурную проблему с этим подходом.


Теперь я не уверен, должен ли я повторно объявлять все параметры класса профиля consul_client в классе профиля consul_server, что, на мой взгляд, нарушит принцип DRY. Кроме того, при использовании данных Hiera в будущем это может привести к ситуации, когда profile::consul_client::* и profile::consul_server::* будут содержать одни и те же дублирующиеся данные, поскольку клиентскую часть данных придется повторять для обоих профилей.

Добавленное примечание: И дублирование параметров в классе consul_server, вероятно, даже не сработает, поскольку параметры нельзя передавать явно, а только через данные, чтобы включить подобные определения ресурсов, поэтому эти дублированные параметры не могут быть перешел в consul_client класс.

Напротив, в документации указано следующее, но я не уверен, что это относится и к включенным классам профилей (поскольку они могут не быть классами компонентов?):

Профили владеют всеми параметрами класса для своих классов компонентов. Если в профиле отсутствует один, это означает, что вам определенно нужно значение по умолчанию; класс компонента не должен использовать значение из данных Hiera. Если вам нужно установить параметр класса, который ранее был опущен, проведите рефакторинг профиля.


В дополнение к этим мыслям можно также увидеть, как два класса профилей рефакторингуются в обычные классы отдельного модуля, что может помочь увидеть последствия различных подходов к проектированию.


В заключение возникают следующие вопросы:

  • Как должны обрабатываться параметры во вложенных профилях, задаваться исключительно данными hiera отдельно для каждого класса профиля?
  • Как передать параметры вложенным профилям во время модульных тестов? Было бы правильно каким-то образом предоставить фиктивные данные hiera как часть тестового приспособления?
  • Будет ли насмешка над классом профиля consul_client лучшим вариантом?

person Alexander    schedule 06.03.2020    source источник


Ответы (1)


Поскольку профиль consul_client является includeed/requireed профилем consul_server, тест завершился неудачей с отсутствующими параметрами для класса профиля consul_client. Кажется, это указывает на некоторую общую структурную проблему с этим подходом.

Не особенно.

Поймите, что если вы используете подобное включению объявление класса с обязательными параметрами, то эти параметры должны будут получать значения либо посредством автоматической привязки данных, либо через предыдущее объявление того же класса, подобное ресурсу. Это ни в коем случае не является "общей структурной проблемой", хотя, как правило, в любом случае следует передавать параметры классам через Hiera (и, следовательно, автоматическую привязку данных).

Это становится немного сложнее в контексте модульного тестирования. Можно настроить данные Hiera для вашего набора тестов, но вместо этого, вероятно, проще написать предварительное условие. Я сделал что-то очень похожее только сегодня, на самом деле. Пример:

describe 'profile::consul_server' do
  # ...
  context "..." do
    let(:pre_condition) do
      'class { "profile::consul_client": param1 => "value1", param2 => "value2" }'
    end
    let(:params) { { bootstrap_expect: 3 } }

    it do
      is_expected.to # ...
    end
  end
end

ОДНАКО, хотя здесь нет особой структурной проблемы, существует вероятная семантическая проблема. Если ваш профиль consul_server включает ваш профиль consul_client, это означает, что каждый сервер консула также должен быть клиентом консула. Я не так хорошо знаком с консулом, поэтому не могу быть уверен, что это неразумно, но, по крайней мере, подозрительно. Если серверы не обязательно являются клиентами, то код, совместно используемый двумя профилями, дублируется лишь случайно, и устранение такого случайного дублирования, скорее всего, вызовет проблемы, а не решит их.

Теперь я не уверен, должен ли я заново объявлять все параметры класса профиля consul_client в классе профиля consul_server, что, на мой взгляд, нарушит принцип DRY. Кроме того, при использовании данных Hiera в будущем это может привести к ситуации, когда profile::consul_client::* и profile::consul_server::* будут содержать одни и те же дублирующиеся данные, поскольку клиентскую часть данных придется повторять для обоих профилей.

Это разумные опасения. Лично я минимизирую количество параметров, которые имеют классы моих профилей. У большинства вообще нет. Мои классы компонентов параметризованы, и их значения параметров в основном поступают из автоматической привязки данных. Мои классы профилей редко используют ресурсоподобные объявления классов компонентов, но когда они это делают, это почти всегда определяется идентичностью профиля, а не его параметрами.

Кроме того, вы можете обнаружить, что рефакторинг облегчит вашу проблему. Если между вашими консул-серверами и клиентами есть общие сведения о конфигурации, то они, вероятно, подойдут для управления через один или несколько классов компонентов, которые объявляются в обоих профилях. Тогда нет необходимости в дублировании данных, потому что это параметры тех общих классов, к которым вы будете привязывать данные.

person John Bollinger    schedule 06.03.2020
comment
Спасибо во-первых - это много, чтобы пройти корыто. Я начну пробовать часть let(:pre_condition), так как это именно то, чего мне не хватало для успешного модульного тестирования. Что касается Consul, терминология действительно немного странная, но клиентская часть определенно присутствует (предоставление HTTP API и т. д.) даже для сервера, так что по сути каждый сервер также является клиентом. Тем не менее, подход класса компонентов только для данных очень интересен, я тоже посмотрю, как он пойдет. Я постараюсь разобраться с этим и отчитаюсь. - person Alexander; 09.03.2020