Puppet Class: sap

Inherits:
sap::params
Defined in:
manifests/init.pp

Summary

Installs prerequisites for an SAP environment on RedHat derivatives

Overview

This module manages SAP prerequisites for several types of SAP installations based on corresponding SAP OSS notes

Examples:

Application server containing an ADS instance and a Netweaver instance

class { 'sap':
  system_ids       => {
    'PR0'          => {
      'components'       => [
         'base',
         'base-extended',
         'ads',
      ],
      'backend_database' => [ 'db2' ],
    },
  },
}

Parameters:

  • system_ids (Hash[Sap::SID, Sap::SIDConfigEntry])

    An hash of SAP system IDs (SIDs) which will be present on the target host along with the following possible attributes for each SID:

    • ‘components` - a list of associated sap applications to enable for this

      SID (mandatory)
      
    • ‘backend_databases` - types of databases to which this system connects

      (mandatory for SIDs with 'base' enabled)
      
    • ‘manage_profiles` - Flag indicating whether the instance profiles for this

      should be managed by the module. If specified
      `instances`, `databases`, and `primary_backend_db`
      become mandatory parameters!
      
    • ‘exclude_common_mount_points` - flag indicating if this SID should skip

      SID specific entries for the 'common'
      mountpoint. Some system types do not have
      /sapmnt directories (e.g. a DAA instance
      or liveCache)
      
    • ‘pattern_map` - A hash mapping pattern lookup keys to regular expressions

      which are unique to this SID. Note that if one is added it
      must have a corresponding value added in `pattern_values`
      
    • ‘pattern_values` - Mapping from a pattern lookup key to the value which

      should be provided wherever the corresponding pattern
      is found.
      
    • ‘instances` - Hash detailing the instances which are part of this SID.

      Entries are keyed by `instance class` and contain the
      corresponding Sap::SidInstance detail. See [Data Types in
      the readme](./README.md#data-types) for detail.
      
    • ‘databases` - Hash detailing the database parameters for this SID.

      Entries in this hash are keyed by `instance class`. For
      detail, see [Data Types in the readme](./README.md#data-types)
      
    • ‘primary_backend_db` - Sap::InstClassDb of the primary backend database

      for this node
      
    • ‘default_pfl_entries` - Hash of profile keys and the desired value which

      will be appended to the end of the DEFAULT.PFL
      file for this instance. Currently not validated.
      
    • ‘sec_info_rules` - Array of Sap::SecInfo entries which will be inserted

      before the final rules in the secinfo file.
      
    • ‘reg_info_rules` - Array of Sap::RegInfo entries which will be inserted

      before the final rules in the reginfo file.
      
    • ‘db2_query_schedules` - Array of Sap::Db2QuerySchedule entries describing

      a query script file, associated parameters, and
      the desired cron schedule entry for each. These
      queries are then configured to execute as the
      DB2 instance owner on the host system.
      

    Note that each entry must be exactly 3 characters in length and contain exclusively uppercase characters. Also, if ‘base` is included in the components list for an SID at least one backend_database entry must be provided.

  • pattern_map (Hash[String, String])

    Hash mapping a given global key such as ‘sid_upper’ to the corresponding regexp used to substitute this value in standard strings. See the mount_point defined type for detail. Note that the values in this hash will be converted to regular expressions via ‘Regexp($value)`

  • create_mount_points (Boolean) (defaults to: false)

    Indicates whether the standard mount points should be created for the specified instance.

  • mount_points (Hash[Sap::SapComponent, Sap::MountPoints]) (defaults to: {})

    Defines the mount points and supporting directories which should be created for each component type. Note that this structure is a deep hash with a ‘–` knockout value.

  • router_oss_realm (Optional[String]) (defaults to: undef)

    Specify OSS realm for SAP router connection. For example, ‘’p:CN=hostname.domain.tld, OU=0123456789, OU=SAProuter, O=SAP, C=DE’‘

  • manage_mount_dependencies (Boolean) (defaults to: false)

    When enabled this module will install and configure the puppet-nfs module detailed here: forge.puppet.com/derdanne/nfs Note that currently only NFSv4 is supported for clients.

  • router_rules (Optional[Array[String]]) (defaults to: undef)

    Specify array of rules for the SAP router

  • distro_text (Optional[String]) (defaults to: undef)

    Modify text in /etc/redhat-release

  • component_packages (Hash[Sap::SapComponent, Array[String]]) (defaults to: {})

    Hash mapping each component type to an array containing the names of the requried packages on this operating system.

  • backenddb_packages (Hash[Sap::BackendDatabase, Array[String]]) (defaults to: {})

    Hash mapping each backend database type to an array containing the names of the requried packages on this operating system.

  • message_servers (Hash[Sap::SID, Sap::MessageServer]) (defaults to: {})

    Hash detailing the message server connection definitions for other systems this particular instance will need to communicate with. For entry structure detail See [Data Types in the readme](./README.md#data-types).

  • bash_source_profile (Boolean) (defaults to: false)

    When enabled the ‘.profile` generated by the SAP installer will be sourced by the .bash_profile of each sidadm user if that file exists.

  • manage_firewall (Boolean) (defaults to: false)

    When enabled all relevant firewall rules for the local system will be created and managed via firewalld.

  • deployed_fonts (Sap::SourcedFile) (defaults to: {})

    Hash with a list of fonts to deploy and their source locations. Each file in this structure will be installed in ‘/usr/sap/$SAPSYSTEMNAME/SYS/global/AdobeDocumentServices/FontManagerService/fonts/customer/` for each local SID with the ADS component enabled.

  • deployed_xdc (Sap::SourcedFile) (defaults to: {})

    Hash with a list of xdc files to deploy and their source locations. Each file in this structure will be installed in ‘/usr/sap/$sid/SYS/global/AdobeDocumentServices/lib/XDC/Customer/` for each local SID with the ADS component enabled.

Author:

  • Phil DeMonaco <pdemon@gmail.com>



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'manifests/init.pp', line 134

class sap (
  Hash[Sap::SID, Sap::SIDConfigEntry] $system_ids,
  Hash[String, String] $pattern_map,
  Boolean $create_mount_points                                  = false,
  Hash[Sap::SapComponent, Sap::MountPoints] $mount_points       = {},
  Boolean $manage_mount_dependencies                            = false,
  Optional[String] $router_oss_realm                            = undef,
  Optional[Array[String]] $router_rules                         = undef,
  Optional[String] $distro_text                                 = undef,
  Hash[Sap::SapComponent, Array[String]] $component_packages    = {},
  Hash[Sap::BackendDatabase, Array[String]] $backenddb_packages = {},
  Hash[Sap::SID, Sap::MessageServer] $message_servers           = {},
  Boolean $bash_source_profile                                  = false,
  Boolean $manage_firewall                                      = false,
  Sap::SourcedFile $deployed_fonts                              = {},
  Sap::SourcedFile $deployed_xdc                                = {},
) inherits sap::params {
  # Validate message server entries
  $sap::message_servers.each |$sid, $data| {
    $mandatory = ['class', 'number']

    # Ensure all mandatory keys are present
    $mandatory.each |$key| {
      unless($key in $data) {
        $message = @("EOT"/L)
          message_server: '${sid}' entry missing mandatory parameter '${key}'!
          |-EOT
        fail($message)
      }
    }

    # Check the content of all present keys
    $data.each |$key, $value| {
      case $key {
        'class': {
          $type = Sap::InstClassSapCs
          $type_name = 'Sap::InstClassSapCs'
        }
        'number': {
          $type = Sap::InstNumber
          $type_name = 'Sap::InstNumber'
        }
        'port_override': {
          $type = Stdlib::Port
          $type_name = 'Stdlib::Port'
        }
        default: {} # impossible to reach case
      }

      unless($value =~ $type) {
        $message = @("EOT"/L)
          message_server: '${sid}' entry parameter '${key}' must be type \
          '${type_name}'!
          |-EOT
        fail($message)
      }
    }
  }

  # Validate SID configuration entries
  $sap::system_ids.each |$sid, $data| {
    # Validate top-level parameters
    $mandatory_sid_data = ['components', 'manage_profiles']
    $type_map_sid_data = {
      'components' => {
        'type'   => Array[Sap::SapComponent],
        'name'   => 'Array[Sap::SapComponent]',
      },
      'backend_databases' => {
        'type'   => Array[Sap::BackendDatabase],
        'name'   => 'Array[Sap::BackendDatabase]',
      },
      'exclude_common_mount_points' => {
        'type'   => Boolean,
        'name'   => 'Boolean',
      },
      'pattern_map' => {
        'type'   => Hash[String, String],
        'name'   => 'Hash[String, String]',
      },
      'pattern_values' => {
        'type'   => Hash[String, String],
        'name'   => 'Hash[String, String]',
      },
      'instances' => {
        'type'   => Hash[Sap::InstClassSap, Sap::SIDInstance],
        'name'   => 'Hash[Sap::InstClassSap, Sap::SIDInstance]',
      },
      'databases' => {
        'type'   => Hash[Sap::InstClassDb, Sap::SIDDatabase],
        'name'   => 'Hash[Sap::InstClassDb, Sap::SIDDatabase]',
      },
      'primary_backend_db' => {
        'type'   => Variant[Sap::InstClassDb, Enum['none']],
        'name'   => 'Variant[Sap::InstClassDb, Enum[none]]',
      },
      'manage_profiles' => {
        'type'   => Boolean,
        'name'   => 'Boolean',
      },
      'default_pfl_entries' => {
        'type'   => Hash[String, String],
        'name'   => 'Hash[String, String]',
      },
      'reg_info_rules' => {
        'type'   => Array[Sap::RegInfo],
        'name'   => 'Array[Sap::RegInfo]',
      },
      'sec_info_rules' => {
        'type'   => Array[Sap::SecInfo],
        'name'   => 'Array[Sap::SecInfo]',
      },
      'db2_query_schedules' => {
        'type'   => Array[Sap::Db2QuerySchedule],
        'name'   => 'Array[Sap::Db2QuerySchedule]',
      },
    }
    # Ensure all mandatory keys are present
    $mandatory_sid_data.each |$key| {
      unless($key in $data) {
        $message = @("EOT"/L)
          '${sid}': entry missing mandatory parameter \
          '${key}'!
          |-EOT
        fail($message)
      }
    }

    # Check the content of all present keys
    $data.each |$key, $value| {
      $type = $type_map_sid_data[$key]['type']
      $type_name = $type_map_sid_data[$key]['name']
      unless($value =~ $type) {
        $message = @("EOT"/L)
          '${sid}': entry parameter '${key}' must be \
          type '${type_name}'!
          |-EOT
        fail($message)
      }
    }

    # Ensure Profile generation parameters are present
    if $data['manage_profiles'] {
      $mandatory_profile_keys = ['primary_backend_db', 'instances']
      $mandatory_profile_keys.each |$key| {
        unless($key in $data) {
          $message = @("EOT"/L)
            '${sid}': parameter '${key}' is mandatory when 'manage_profiles' \
            is enabled!
            |-EOT
          fail($message)
        }
      }

      $primary_backend_db = $data['primary_backend_db']
      case $primary_backend_db {
        'none': {} # Systems with no databases do nothing here
        default: {
          unless('databases' in $data) {
            $message = @("EOT"/L)
              '${sid}': parameter 'databases' is mandatory when \
              'primary_backend_db' is not 'none'!
              |-EOT
            fail($message)
          }

          unless($primary_backend_db in $data['databases']) {
            $message = @("EOT"/L)
              '${sid}': missing primary_backend_db entry \
              '${primary_backend_db}' in 'databases'!
              |-EOT
            fail($message)
          }
        }
      }
    }

    # Validate component dependencies 
    $comps = $data['components']
    # Ensure a backend is present for application servers
    if 'base' in $comps and !('backend_databases' in $data) {
      $message = @("EOT"/L)
        SID ${sid}: All application servers must specify at least one \
        backend database! ('base' implies application server)
        |-EOT
      fail($message)
    }

    # Ensure liveCache_client is present whenever liveCache is included
    if 'liveCache' in $comps and !('liveCache_client' in $comps) {
      $message = @("EOT"/L)
        SID ${sid}: liveCache_client is mandatory for liveCache \
        database nodes!
        |-EOT
      fail($message)
    }

    # Validate instances parameter
    if('instances' in $data) {
      $instances = $data['instances']
    } else {
      $instances = {}
    }
    $instances.each |$inst_class, $inst_data| {
      case $inst_class {
        default: {
          $type_map = {
            'number' => {
              'type' => Sap::InstNumber,
              'name' => 'Sap::InstNumber',
            },
            'types' => {
              'type' => Hash[Sap::InstType, Array[Stdlib::Fqdn]],
              'name' => 'Hash[Sap::InstType, Array[Stdlib::Fqdn]]',
            },
            'host' => {
              'type' => Stdlib::Fqdn,
              'name' => 'Stdlib::Fqdn',
            },
            'legacy_start_profile' => {
              'type' => Boolean,
              'name' => 'Boolean',
            },
          }
        }
      }
      case $inst_class {
        'cs-abap', 'cs-java', 'ers': {
          $mandatory = ['number', 'types', 'host']
        }
        default: {
          $mandatory = ['number', 'types']
        }
      }

      # Ensure all mandatory keys are present
      $mandatory.each |$key| {
        unless($key in $inst_data) {
          $message = @("EOT"/L)
            '${sid}' instance: '${inst_class}' entry missing mandatory parameter \
            '${key}'!
            |-EOT
          fail($message)
        }
      }

      # Check the content of all present keys
      $inst_data.each |$key, $value| {
        $type = $type_map[$key]['type']
        $type_name = $type_map[$key]['name']
        unless($value =~ $type) {
          $message = @("EOT"/L)
            '${sid}' instance: '${inst_class}' entry parameter '${key}' must be \
            type '${type_name}'!
            |-EOT
          fail($message)
        }
      }
    }

    # Validate SID databases
    if('databases' in $data) {
      $databases = $data['databases']
    } else {
      $databases = {}
    }
    $databases.each |$db_class, $db_data| {
      case $db_class {
        'db-db2': {
          $mandatory = ['com_port', 'fcm_base_port', 'nodes', 'ha_mode']
          $type_map = {
            'com_port'      => {
              'type'   => Stdlib::Port,
              'name'   => 'Stdlib::Port',
            },
            'fcm_base_port' => {
              'type'   => Stdlib::Port,
              'name'   => 'Stdlib::Port',
            },
            'hadr_base_port' => {
              'type'   => Stdlib::Port,
              'name'   => 'Stdlib::Port',
            },
            'ha_mode'       => {
              'type'  => Sap::DbHaMode,
              'name'  => 'Sap::DbHaMode',
            },
            'host'          => {
              'type'  => Stdlib::Fqdn,
              'name'  => 'Stdlib::Fqdn',
            },
            'nodes'         => {
              'type' => Array[Stdlib::Fqdn],
              'name' => 'Array[Stdlib::Fqdn]',
            },
          }
        }
        default: { next() } # do nothing
      }

      # Ensure all mandatory keys are present
      $mandatory.each |$key| {
        unless($key in $db_data) {
          $message = @("EOT"/L)
            '${sid}' database: '${db_class}' entry missing mandatory parameter \
            '${key}'!
            |-EOT
          fail($message)
        }
      }

      # Check for HADR configuration details
      case $db_data['ha_mode'] {
        'hadr': {
          unless('hadr_base_port' in $db_data) {
            $message = @("EOT"/L)
              '${sid}' database: '${db_class}' parameter 'hadr_base_port' is \
              mandatory when 'ha_mode' is 'hadr'!
              |-EOT
            fail($message)
          }
        }
        default: {} # nothing to do here yet
      }

      # Check the content of all present keys
      $db_data.each |$key, $value| {
        $type = $type_map[$key]['type']
        $type_name = $type_map[$key]['name']
        unless($value =~ $type) {
          $message = @("EOT"/L)
            '${sid}' database: '${db_class}' entry parameter '${key}' must be \
            type '${type_name}'!
            |-EOT
          fail($message)
        }
      }
    }
  }

  # Build the unique list of enabled_components
  $components_wip = $system_ids.map |$entry| {
    $sid = $entry[0]
    unless('components' in $entry[1]) {
      fail("SID '${sid}' must have at least one component!")
    }
    $entry[1]['components']
  }
  $enabled_components = unique(flatten($components_wip))

  # Build the unique list of backend databases on this node
  $backends_wip = $system_ids.map |$entry| {
    $sid = $entry[0]
    if 'backend_databases' in $entry[1] {
      $entry[1]['backend_databases']
    } else {
      {}
    }
  }
  $backend_databases = unique(flatten($backends_wip))

  # Fail if dependencies are not met
  $base_enabled = 'base' in $enabled_components
  $sap::params::requires_base.each |$component| {
    if ($component in $enabled_components) {
      unless($base_enabled) {
        fail("Component '${component}' requires 'base'!")
      }
    }
  }

  # Fail if a backend_database is not specified for a node which is not
  # 'database only' (we're assuming if base is enabled that's the case)
  if ($base_enabled and empty($backend_databases)) {
    fail('All application servers must specify a backend database!')
  }

  $base_extended_enabled = 'base_extended' in $enabled_components
  $sap::params::requires_base_extended.each |$component| {
    if ($component in $enabled_components) {
      unless($base_extended_enabled) {
        fail("Component '${component}' requires 'base_extended'!")
      }
    }
  }

  # Convert the pattern_map_global values to regular expressions
  $pattern_map_temp = $pattern_map.map |$entry| {
    [$entry[0], Regexp($entry[1])]
  }
  $regex_map = Hash.new(flatten($pattern_map_temp))

  # Construct the default pattern listing
  $temp_sid_case_mapping = $system_ids.map |$entry| {
    $sid = $entry[0]
    $data = $entry[1]

    # Return_value
    [
      $sid,
      {
        'sid_upper' => $sid,
        'sid_lower' => downcase($sid),
      },
    ]
  }
  $sid_case_mapping = Hash.new(flatten($temp_sid_case_mapping))

  # liveCache client cannot be specified for more than one SID
  $lc_sids = $system_ids.filter |$sid, $data| {
    'liveCache_client' in $data['components']
  }
  if $lc_sids.length > 1 {
    fail('At most one liveCache SID can be specified!')
  }



  # Start workflow
  if $facts['os']['family'] == 'RedHat' {
    # Ensure the install, config, and service componets happen within here
    contain sap::install
    contain sap::config
    contain sap::management
    contain sap::service

    Class['sap::install']
    -> Class['sap::config']
    -> Class['sap::management']
    ~> Class['sap::service']
  } else {
    warning('The current operating system is not supported!')
  }
}