summaryrefslogtreecommitdiffstats
path: root/testcases/VIM/OpenStack/CI/libraries/os_defaults.yaml
AgeCommit message (Expand)AuthorFilesLines
2016-02-01Add aodh default user for apexjose.lausuch1-2/+2
2016-01-26Add more OS defaults for Fuel 8.0jose.lausuch1-2/+2
2016-01-20Add default admin_floating for Fuel 8.0jose.lausuch1-2/+2
2016-01-18Add 'neutron' default user for joidjose.lausuch1-1/+1
2016-01-12Add cinderv2 and ceilometer to default users for Apexjose.lausuch1-1/+1
2016-01-11Add ext network for joidMorgan Richomme1-1/+1
2016-01-11Add 'service' tenant to os_defaults for apexjose.lausuch1-1/+1
2016-01-08typo it should be services not service.Narinder Gupta1-1/+1
2016-01-08added the quantum_nova user as well for joid.Narinder Gupta1-1/+1
2016-01-08Add joid defaults to clean openstackjose.lausuch1-3/+3
2016-01-07Move openstack defaults to a yaml filejose.lausuch1-0/+31
n306' href='#n306'>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 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975
{%- set primary_role = [roles[0]] -%}
{%- for role in roles -%}
  {%- if 'primary' in role.tags and 'controller' in role.tags -%}
    {%- set _ = primary_role.pop() -%}
    {%- set _ = primary_role.append(role) -%}
  {%- endif -%}
{%- endfor -%}
{%- set primary_role_name = primary_role[0].name -%}
# primary role is: {{primary_role_name}}
heat_template_version: pike

description: >
  Deploy an OpenStack environment, consisting of several node types (roles),
  Controller, Compute, BlockStorage, SwiftStorage and CephStorage. The Storage
  roles enable independent scaling of the storage components, but the minimal
  deployment is one Controller and one Compute node.


# TODO(shadower): we should probably use the parameter groups to put
# some order in here.
parameters:

  # Common parameters (not specific to a role)
{%- for network in networks if network.vip|default(false) %}
{%- if network.name == 'External' %}
  # Special case the External hostname param, which is CloudName
  CloudName:
    default: overcloud.localdomain
    description: The DNS name of this cloud. E.g. ci-overcloud.tripleo.org
    type: string
{%- elif network.name == 'InternalApi' %}
  # Special case the Internal API hostname param, which is CloudNameInternal
  CloudNameInternal:
    default: overcloud.{{network.name.lower()}}.localdomain
    description: >
      The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
      'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
    type: string
{%- elif network.name == 'StorageMgmt' %}
  # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
  CloudNameStorageManagement:
    default: overcloud.{{network.name.lower()}}.localdomain
    description: >
      The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
      'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
    type: string
{%- else %}
  CloudName{{network.name}}:
    default: overcloud.{{network.name.lower()}}.localdomain
    description: >
      The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
      'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
    type: string
{%- endif %}
{%- endfor %}
  CloudNameCtlplane:
    default: overcloud.ctlplane.localdomain
    description: >
      The DNS name of this cloud's provisioning network endpoint. E.g.
      'ci-overcloud.ctlplane.tripleo.org'.
    type: string
  ExtraConfig:
    default: {}
    description: |
      Additional hiera configuration to inject into the cluster.
    type: json
{%- for role in roles %}
  {{role.name}}ExtraConfig:
    default: {}
    description: |
      Role specific additional hiera configuration to inject into the cluster.
    type: json
{%- if role.deprecated_param_extraconfig is defined %}
  {{role.deprecated_param_extraconfig}}:
    default: {}
    description: |
      DEPRECATED use {{role.name}}ExtraConfig instead
    type: json
{%- endif %}
{%- endfor %}
  NeutronControlPlaneID:
    default: 'ctlplane'
    type: string
    description: Neutron ID or name for ctlplane network.
  NeutronPublicInterface:
    default: nic1
    description: Which interface to add to the NeutronPhysicalBridge.
    type: string
  ControlFixedIPs:
    default: []
    description: >
        Control the IP allocation for the ControlVirtualIP port. E.g.
        [{'ip_address':'1.2.3.4'}]
    type: json
{%- for network in networks if network.vip|default(false) %}
{%- if network.name == 'External' %}
  # TODO (dsneddon) Legacy name, eventually refactor to match network name
  PublicVirtualFixedIPs:
    default: []
    description: >
        Control the IP allocation for the PublicVirtualInterface port. E.g.
        [{'ip_address':'1.2.3.4'}]
    type: json
{%- else %}
  {{network.name}}VirtualFixedIPs:
    default: []
    description: >
        Control the IP allocation for the {{network.name}}VirtualInterface port. E.g.
        [{'ip_address':'1.2.3.4'}]
    type: json
{%- endif %}
{%- endfor %}
  RabbitCookieSalt:
    type: string
    default: unset
    description: Salt for the rabbit cookie, change this to force the randomly generated rabbit cookie to change.
  RedisVirtualFixedIPs:
    default: []
    description: >
        Control the IP allocation for the virtual IP used by Redis. E.g.
        [{'ip_address':'1.2.3.4'}]
    type: json
  CloudDomain:
    default: 'localdomain'
    type: string
    description: >
      The DNS domain used for the hosts. This must match the
      overcloud_domain_name configured on the undercloud.
  ServerMetadata:
    default: {}
    description: >
      Extra properties or metadata passed to Nova for the created nodes in
      the overcloud. It's accessible via the Nova metadata API.
    type: json

# Compute-specific params
# FIXME(shardy) handle these deprecated names as they don't match compute.yaml
  HypervisorNeutronPhysicalBridge:
    default: 'br-ex'
    description: >
      An OVS bridge to create on each hypervisor. This defaults to br-ex the
      same as the control plane nodes, as we have a uniform configuration of
      the openvswitch agent. Typically should not need to be changed.
    type: string
  HypervisorNeutronPublicInterface:
    default: nic1
    description: What interface to add to the HypervisorNeutronPhysicalBridge.
    type: string

  NodeCreateBatchSize:
    default: 30
    description: Maxiumum batch size for creating nodes
    type: number

  # Jinja loop for Role in role_data.yaml
{% for role in roles %}
  # Parameters generated for {{role.name}} Role
  {{role.name}}Services:
    description: A list of service resources (configured in the Heat
                 resource_registry) which represent nested stacks
                 for each service that should get installed on the {{role.name}} role.
    type: comma_delimited_list

  {{role.name}}Count:
    description: Number of {{role.name}} nodes to deploy
    type: number
    default: {{role.CountDefault|default(0)}}

  {{role.name}}HostnameFormat:
    type: string
    description: >
      Format for {{role.name}} node hostnames
      Note %index% is translated into the index of the node, e.g 0/1/2 etc
      and %stackname% is replaced with the stack name e.g overcloud
  {% if role.HostnameFormatDefault %}
    default: "{{role.HostnameFormatDefault}}"
  {% else %}
    default: "%stackname%-{{role.name.lower()}}-%index%"
  {% endif %}
  {{role.name}}RemovalPolicies:
    default: []
    type: json
    description: >
      List of resources to be removed from {{role.name}} ResourceGroup when
      doing an update which requires removal of specific resources.
      Example format ComputeRemovalPolicies: [{'resource_list': ['0']}]

  {{role.name}}SchedulerHints:
    type: json
    description: Optional scheduler hints to pass to nova
    default: {}
{%- if role.deprecated_param_scheduler_hints is defined %}
  {{role.deprecated_param_scheduler_hints}}:
    type: json
    description: DEPRECATED - use {{role.name}}SchedulerHints instead
    default: {}
{%- endif %}

  {{role.name}}Parameters:
    type: json
    description: Optional Role Specific parameters to be provided to service
    default: {}
{% endfor %}

  # Identifiers to trigger tasks on nodes
  UpdateIdentifier:
    default: ''
    type: string
    description: >
      Setting to a previously unused value during stack-update will trigger
      package update on all nodes
  DeployIdentifier:
    default: ''
    type: string
    description: >
      Setting this to a unique value will re-run any deployment tasks which
      perform configuration on a Heat stack-update.
  AddVipsToEtcHosts:
    default: True
    type: boolean
    description: >
      Set to true to append per network Vips to /etc/hosts on each node.

  DeploymentServerBlacklist:
    default: []
    type: comma_delimited_list
    description: >
      List of server hostnames to blacklist from any triggered deployments.

{% for role in roles %}
{%- if role.deprecated_param_scheduler_hints is defined or role.deprecated_param_extraconfig is defined %}
{%- if not parameter_groups_defined|default(false) %}
parameter_groups:
- label: deprecated
  description: Do not use deprecated params, they will be removed.
  parameters:
{%- set parameter_groups_defined = true %}
{%- endif %}
{%- endif %}
{%- if role.deprecated_param_scheduler_hints is defined %}
    - {{role.deprecated_param_scheduler_hints}}
{%- endif %}
{%- if role.deprecated_param_extraconfig is defined %}
    - {{role.deprecated_param_extraconfig}}
{%- endif %}
{%- endfor %}

conditions:
  add_vips_to_etc_hosts: {equals : [{get_param: AddVipsToEtcHosts}, True]}

resources:

  VipHosts:
    type: OS::Heat::Value
    properties:
      type: string
      value:
        list_join:
        - "\n"
        - - str_replace:
              template: IP  HOST
              params:
                IP: {get_attr: [VipMap, net_ip_map, ctlplane]}
                HOST: {get_param: CloudNameCtlplane}
{%- for network in networks if network.vip|default(false) %}
{%- if network.name == 'External' %}
  # Special case the External hostname param, which is CloudName
          - str_replace:
              template: IP  HOST
              params:
                IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
                HOST: {get_param: CloudName}
{%- elif network.name == 'InternalApi' %}
  # Special case the Internal API hostname param, which is CloudNameInternal
          - str_replace:
              template: IP  HOST
              params:
                IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
                HOST: {get_param: CloudNameInternal}
{%- elif network.name == 'StorageMgmt' %}
  # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
          - str_replace:
              template: IP  HOST
              params:
                IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
                HOST: {get_param: CloudNameStorageManagement}
{%- else %}
          - str_replace:
              template: IP  HOST
              params:
                IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
                HOST: {get_param: CloudName{{network.name}}}
{%- endif %}
{%- endfor %}

  HeatAuthEncryptionKey:
    type: OS::TripleO::RandomString

  PcsdPassword:
    type: OS::TripleO::RandomString
    properties:
      length: 16

  HorizonSecret:
    type: OS::TripleO::RandomString
    properties:
      length: 10

  NetCidrMapValue:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_replace:
        - map_merge:
          - {get_attr: [Networks, net_cidr_map]}
          - ctlplane: {get_attr: [ControlVirtualIP, subnets, 0, cidr]}
        - keys:
            ctlplane: {get_param: NeutronControlPlaneID}
          values:
            disabled: {get_attr: [ControlVirtualIP, subnets, 0, cidr]}

  ServiceNetMap:
    type: OS::TripleO::ServiceNetMap

  EndpointMap:
    type: OS::TripleO::EndpointMap
    properties:
      CloudEndpoints:
        ctlplane: {get_param: CloudNameCtlplane}
{%- for network in networks if network.vip|default(false) %}
{%- if network.name == 'External' %}
  # Special case the External hostname param, which is CloudName
        {{network.name_lower}}: {get_param: CloudName}
{%- elif network.name == 'InternalApi' %}
  # Special case the Internal API hostname param, which is CloudNameInternal
        {{network.name_lower}}: {get_param: CloudNameInternal}
{%- elif network.name == 'StorageMgmt' %}
  # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
        {{network.name_lower}}: {get_param: CloudNameStorageManagement}
{%- else %}
        {{network.name_lower}}: {get_param: CloudName{{network.name}}}
{%- endif %}
{%- endfor %}
      NetIpMap: {get_attr: [VipMap, net_ip_map]}
      ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}

  EndpointMapData:
    type: OS::Heat::Value
    properties:
      type: json
      value: {get_attr: [EndpointMap, endpoint_map]}

  SshKnownHostsConfig:
    type: OS::TripleO::Ssh::KnownHostsConfig
    properties:
      known_hosts:
        list_join:
          - ''
          {% for role in roles %}
          - {get_attr: [{{role.name}}, known_hosts_entry]}
          {% endfor %}

  # Jinja loop for Role in roles_data.yaml
{% for role in roles %}
  # Resources generated for {{role.name}} Role
  {{role.name}}ServiceChain:
    type: OS::TripleO::Services
    properties:
      Services:
        get_param: {{role.name}}Services
      ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
      ServiceData:
        net_cidr_map: {get_attr: [NetCidrMapValue, value]}
      EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
      DefaultPasswords: {get_attr: [DefaultPasswords, passwords]}
      RoleName: {{role.name}}
      RoleParameters: {get_param: {{role.name}}Parameters}

  # Lookup of role_data via heat outputs is slow, so workaround this by caching
  # the value in an OS::Heat::Value resource
  {{role.name}}ServiceChainRoleData:
    type: OS::Heat::Value
    properties:
      type: json
      value: {get_attr: [{{role.name}}ServiceChain, role_data]}

  {{role.name}}ServiceConfigSettings:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_merge:
          - get_attr: [{{role.name}}ServiceChainRoleData, value, config_settings]
          {% for r in roles %}
          - get_attr: [{{r.name}}ServiceChainRoleData, value, global_config_settings]
          {% endfor %}
          # This next step combines two yaql passes:
          # - The inner one does a deep merge on the service_config_settings for all roles
          # - The outer one filters the map based on the services enabled for the role
          #   then merges the result into one map.
          - yaql:
              expression: let(root => $) -> $.data.map.items().where($[0] in coalesce($root.data.services, [])).select($[1]).reduce($1.mergeWith($2), {})
              data:
                map:
                  yaql:
                    expression: $.data.where($ != null).reduce($1.mergeWith($2), {})
                    data:
                    {% for r in roles %}
                      - get_attr: [{{r.name}}ServiceChainRoleData, value, service_config_settings]
                    {% endfor %}
                services: {get_attr: [{{role.name}}ServiceNames, value]}

  {{role.name}}MergedConfigSettings:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        config_settings: {}
        global_config_settings: {}
        service_config_settings: {}
        merged_config_settings:
          map_merge:
          - get_attr: [{{role.name}}ServiceConfigSettings, value]
          - get_param: ExtraConfig
{%- if role.deprecated_param_extraconfig is defined %}
          - get_param: {{role.deprecated_param_extraconfig}}
{%- endif %}
          - get_param: {{role.name}}ExtraConfig

  # Filter any null/None service_names which may be present due to mapping
  # of services to OS::Heat::None
  {{role.name}}ServiceNames:
    type: OS::Heat::Value
    depends_on: {{role.name}}ServiceChain
    properties:
      type: comma_delimited_list
      value:
        yaql:
          expression: coalesce($.data, []).where($ != null)
          data: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_names]}

  {{role.name}}HostsDeployment:
    type: OS::Heat::StructuredDeployments
    properties:
      name: {{role.name}}HostsDeployment
      config: {get_attr: [hostsConfig, config_id]}
      servers: {get_attr: [{{role.name}}Servers, value]}

  {{role.name}}SshKnownHostsDeployment:
    type: OS::Heat::StructuredDeployments
    properties:
      name: {{role.name}}SshKnownHostsDeployment
      config: {get_resource: SshKnownHostsConfig}
      servers: {get_attr: [{{role.name}}Servers, value]}

  {{role.name}}AllNodesDeployment:
    type: OS::TripleO::AllNodesDeployment
    depends_on:
{% for role_inner in roles %}
      - {{role_inner.name}}HostsDeployment
{% endfor %}
    properties:
      name: {{role.name}}AllNodesDeployment
      config: {get_attr: [allNodesConfig, config_id]}
      servers: {get_attr: [{{role.name}}Servers, value]}
      input_values:
        # Note we have to use yaql to look up the first hostname/ip in the
        # list because heat path based attributes operate on the attribute
        # inside the ResourceGroup, not the exposed list ref discussion in
        # https://bugs.launchpad.net/heat/+bug/1640488
        # The coalesce is needed because $.data is None during heat validation
        bootstrap_nodeid:
          yaql:
            expression: coalesce($.data, []).first(null)
            data: {get_attr: [{{role.name}}, hostname]}
        bootstrap_nodeid_ip:
          yaql:
            expression: coalesce($.data, []).first(null)
            data: {get_attr: [{{role.name}}, ip_address]}

  {{role.name}}AllNodesValidationDeployment:
    type: OS::Heat::StructuredDeployments
    depends_on: {{role.name}}AllNodesDeployment
    properties:
      name: {{role.name}}AllNodesValidationDeployment
      config: {get_resource: AllNodesValidationConfig}
      servers: {get_attr: [{{role.name}}Servers, value]}

  {{role.name}}IpListMap:
    type: OS::TripleO::Network::Ports::NetIpListMap
    properties:
      ControlPlaneIpList: {get_attr: [{{role.name}}, ip_address]}
{%- for network in networks if network.enabled|default(true) %}
      {{network.name}}IpList: {get_attr: [{{role.name}}, {{network.name_lower}}_ip_address]}
{%- endfor %}
      EnabledServices: {get_attr: [{{role.name}}ServiceNames, value]}
      ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
      ServiceHostnameList: {get_attr: [{{role.name}}, hostname]}
      NetworkHostnameMap: {get_attr: [{{role.name}}NetworkHostnameMap, value]}

  {{role.name}}NetworkHostnameMap:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        # Note (shardy) this somewhat complex yaql may be replaced
        # with a map_deep_merge function in ocata.  It merges the
        # list of maps, but appends to colliding lists so we can
        # create a map of lists for all nodes for each network
        yaql:
          expression: dict($.data.where($ != null).flatten().selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
          data:
            - {get_attr: [{{role.name}}, hostname_map]}

  {{role.name}}:
    type: OS::Heat::ResourceGroup
    depends_on: Networks
    update_policy:
      batch_create:
        max_batch_size: {get_param: NodeCreateBatchSize}
    properties:
      count: {get_param: {{role.name}}Count}
      removal_policies: {get_param: {{role.name}}RemovalPolicies}
      resource_def:
        type: OS::TripleO::{{role.name}}
        properties:
          CloudDomain: {get_param: CloudDomain}
          ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
          EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
          Hostname:
            str_replace:
              template: {get_param: {{role.name}}HostnameFormat}
              params:
                '%stackname%': {get_param: 'OS::stack_name'}
          NodeIndex: '%index%'
          # Note, SchedulerHints must be defined here, not only in the
          # nested template, as it can contain %index%
          {{role.name}}SchedulerHints:
            map_merge:
{%- if role.deprecated_param_scheduler_hints is defined %}
              - {get_param: {{role.deprecated_param_scheduler_hints}}}
{%- endif %}
              - {get_param: {{role.name}}SchedulerHints}
          ServiceConfigSettings: {get_attr: [{{role.name}}ServiceConfigSettings, value]}
          ServiceNames: {get_attr: [{{role.name}}ServiceNames, value]}
          MonitoringSubscriptions: {get_attr: [{{role.name}}ServiceChainRoleData, value, monitoring_subscriptions]}
          LoggingSources: {get_attr: [{{role.name}}ServiceChainRoleData, value, logging_sources]}
          LoggingGroups: {get_attr: [{{role.name}}ServiceChainRoleData, value, logging_groups]}
          ServiceMetadataSettings: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_metadata_settings]}
          DeploymentServerBlacklistDict: {get_attr: [DeploymentServerBlacklistDict, value]}
          RoleParameters: {get_param: {{role.name}}Parameters}
{% endfor %}

{% for role in roles %}
  {{role.name}}Servers:
    type: OS::Heat::Value
    depends_on: {{role.name}}
    properties:
      type: json
      value:
        yaql:
          expression: let(servers=>switch(isDict($.data.servers) => $.data.servers, true => {})) -> $servers.deleteAll($servers.keys().where($servers[$] = null))
          data:
            servers: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
{% endfor %}

  # This is a different format to *Servers, as it creates a map of lists
  # whereas *Servers creates a map of maps with keys of the nested resource names
  ServerIdMap:
    type: OS::Heat::Value
    properties:
      value:
        server_ids:
{% for role in roles %}
          {{role.name}}: {get_attr: [{{role.name}}, nova_server_resource]}
{% endfor %}
        bootstrap_server_id:
          yaql:
            expression: coalesce($.data, []).first(null)
            data: {get_attr: [{{primary_role_name}}, nova_server_resource]}

  # This resource just creates a dict out of the DeploymentServerBlacklist,
  # which is a list. The dict is used in the role templates to set a condition
  # on whether to create the deployment resources. We can't use the list
  # directly because there is no way to ask Heat if a list contains a specific
  # value.
  DeploymentServerBlacklistDict:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_merge:
          repeat:
            template:
              hostname: 1
            for_each:
              hostname: {get_param: DeploymentServerBlacklist}

  hostsConfig:
    type: OS::TripleO::Hosts::SoftwareConfig
    properties:
      hosts:
        list_join:
        - "\n"
        - - if:
            - add_vips_to_etc_hosts
            - {get_attr: [VipHosts, value]}
            - ''
        -
{% for role in roles %}
          - list_join:
            - ""
            - {get_attr: [{{role.name}}, hosts_entry]}
{% endfor %}

  allNodesConfig:
    type: OS::TripleO::AllNodes::SoftwareConfig
    properties:
{%- for network in networks if network.vip|default(false) %}
{%- if network.name == 'External' %}
  # Special case the External hostname param, which is CloudName
      cloud_name_{{network.name_lower}}: {get_param: CloudName}
{%- elif network.name == 'InternalApi' %}
  # Special case the Internal API hostname param, which is CloudNameInternal
      cloud_name_{{network.name_lower}}: {get_param: CloudNameInternal}
{%- elif network.name == 'StorageMgmt' %}
  # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
      cloud_name_{{network.name_lower}}: {get_param: CloudNameStorageManagement}
{%- else %}
      cloud_name_{{network.name_lower}}: {get_param: CloudName{{network.name}}}
{%- endif %}
{%- endfor %}
      cloud_name_ctlplane: {get_param: CloudNameCtlplane}
      enabled_services:
        list_join:
          - ','
{% for role in roles %}
          - {get_attr: [{{role.name}}ServiceNames, value]}
{% endfor %}
      controller_ips: {get_attr: [{{primary_role_name}}, ip_address]}
      controller_names: {get_attr: [{{primary_role_name}}, hostname]}
      service_ips:
        # Note (shardy) this somewhat complex yaql may be replaced
        # with a map_deep_merge function in ocata.  It merges the
        # list of maps, but appends to colliding lists when a service
        # is deployed on more than one role
        yaql:
          expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
          data:
            l:
{% for role in roles %}
              - {get_attr: [{{role.name}}IpListMap, service_ips]}
{% endfor %}
      service_node_names:
        yaql:
          expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
          data:
            l:
{% for role in roles %}
              - {get_attr: [{{role.name}}IpListMap, service_hostnames]}
{% endfor %}
      short_service_node_names:
        yaql:
          expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
          data:
            l:
{% for role in roles %}
              - {get_attr: [{{role.name}}IpListMap, short_service_hostnames]}
{% endfor %}
      short_service_bootstrap_node:
        yaql:
          expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten().first()]))
          data:
            l:
{% for role in roles %}
              - {get_attr: [{{role.name}}IpListMap, short_service_bootstrap_hostnames]}
{% endfor %}
      NetVipMap: {get_attr: [VipMap, net_ip_map]}
      RedisVirtualIP: {get_attr: [RedisVirtualIP, ip_address]}
      ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
      DeployIdentifier: {get_param: DeployIdentifier}
      UpdateIdentifier: {get_param: UpdateIdentifier}

  MysqlRootPassword:
    type: OS::TripleO::RandomString
    properties:
      length: 10

  RabbitCookie:
    type: OS::TripleO::RandomString
    properties:
      length: 20
      salt: {get_param: RabbitCookieSalt}

  DefaultPasswords:
    type: OS::TripleO::DefaultPasswords
    properties:
      DefaultMysqlRootPassword: {get_attr: [MysqlRootPassword, value]}
      DefaultRabbitCookie: {get_attr: [RabbitCookie, value]}
      DefaultHeatAuthEncryptionKey: {get_attr: [HeatAuthEncryptionKey, value]}
      DefaultPcsdPassword: {get_attr: [PcsdPassword, value]}
      DefaultHorizonSecret: {get_attr: [HorizonSecret, value]}

  # creates the network architecture
  Networks:
    type: OS::TripleO::Network

  ControlVirtualIP:
    type: OS::TripleO::Network::Ports::ControlPlaneVipPort
    depends_on: Networks
    properties:
      name: control_virtual_ip
      network: {get_param: NeutronControlPlaneID}
      fixed_ips: {get_param: ControlFixedIPs}
      replacement_policy: AUTO

  RedisVirtualIP:
    depends_on: Networks
    type: OS::TripleO::Network::Ports::RedisVipPort
    properties:
      ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
      ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
      PortName: redis_virtual_ip
      NetworkName: {get_attr: [ServiceNetMap, service_net_map, RedisNetwork]}
      ServiceName: redis
      FixedIPs: {get_param: RedisVirtualFixedIPs}

{%- for network in networks if network.vip|default(false) %}
{%- if network.name == 'External' %}
  # The public VIP is on the External net, falls back to ctlplane
  PublicVirtualIP:
    depends_on: Networks
    type: OS::TripleO::Network::Ports::ExternalVipPort
    properties:
      ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
      ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
      PortName: public_virtual_ip
      FixedIPs: {get_param: PublicVirtualFixedIPs}
{%- elif network.name == 'StorageMgmt' %}
  {{network.name}}VirtualIP:
    depends_on: Networks
    type: OS::TripleO::Network::Ports::{{network.name}}VipPort
    properties:
      ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
      PortName: storage_management_virtual_ip
      FixedIPs: {get_param: {{network.name}}VirtualFixedIPs}
{%- else %}
  {{network.name}}VirtualIP:
    depends_on: Networks
    type: OS::TripleO::Network::Ports::{{network.name}}VipPort
    properties:
      ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
      PortName: {{network.name_lower}}_virtual_ip
      FixedIPs: {get_param: {{network.name}}VirtualFixedIPs}
{%- endif %}
{%- endfor %}

  VipMap:
    type: OS::TripleO::Network::Ports::NetVipMap
    properties:
      ControlPlaneIp: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
{%- for network in networks if network.vip|default(false) %}
{%- if network.name == 'External' %}
      ExternalIp: {get_attr: [PublicVirtualIP, ip_address]}
      ExternalIpUri: {get_attr: [PublicVirtualIP, ip_address_uri]}
{%- else %}
      {{network.name}}Ip: {get_attr: [{{network.name}}VirtualIP, ip_address]}
      {{network.name}}IpUri: {get_attr: [{{network.name}}VirtualIP, ip_address_uri]}
{%- endif %}
{%- endfor %}
      # No tenant or management VIP required
    # Because of nested get_attr functions in the KeystoneAdminVip output, we
    # can't determine which attributes of VipMap are used until after
    # ServiceNetMap's attribute values are available.
    depends_on: ServiceNetMap

  # All Nodes Validations
  AllNodesValidationConfig:
    type: OS::TripleO::AllNodes::Validation
    properties:
      PingTestIps:
        list_join:
        - ' '
        -
{%- for network in networks if network.enabled|default(true) %}
          - yaql:
              expression: coalesce($.data, []).first(null)
              data: {get_attr: [{{primary_role_name}}, {{network.name_lower}}_ip_address]}
{%- endfor %}

  UpdateWorkflow:
    type: OS::TripleO::Tasks::UpdateWorkflow
    depends_on:
{% for role in roles %}
      - {{role.name}}AllNodesDeployment
{% endfor %}
    properties:
      servers:
{% for role in roles %}
        {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
{% endfor %}
      input_values:
        deploy_identifier: {get_param: DeployIdentifier}
        update_identifier: {get_param: UpdateIdentifier}

  # Optional ExtraConfig for all nodes - all roles are passed in here, but
  # the nested template may configure each role differently (or not at all)
  AllNodesExtraConfig:
    type: OS::TripleO::AllNodesExtraConfig
    depends_on:
      - UpdateWorkflow
{% for role in roles %}
      - {{role.name}}AllNodesValidationDeployment
{% endfor %}
    properties:
      servers:
{% for role in roles %}
        {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
{% endfor %}

  # Post deployment steps for all roles
  AllNodesDeploySteps:
    type: OS::TripleO::PostDeploySteps
    depends_on:
      - AllNodesExtraConfig
{% for role in roles %}
      - {{role.name}}AllNodesDeployment
{% endfor %}
    properties:
      servers:
{% for role in roles %}
        {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
{% endfor %}
      stack_name: {get_param: 'OS::stack_name'}
      EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
      ctlplane_service_ips:
        # Note (shardy) this somewhat complex yaql may be replaced
        # with a map_deep_merge function in ocata.  It merges the
        # list of maps, but appends to colliding lists when a service
        # is deployed on more than one role
        yaql:
          expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
          data:
            l:
{% for role in roles %}
              - {get_attr: [{{role.name}}IpListMap, ctlplane_service_ips]}
{% endfor %}
      role_data:
{% for role in roles %}
        {{role.name}}:
          map_merge:
          - {get_attr: [{{role.name}}ServiceChainRoleData, value]}
          - {get_attr: [{{role.name}}MergedConfigSettings, value]}
{% endfor %}

  ServerOsCollectConfigData:
    type: OS::Heat::Value
    properties:
      type: json
      value:
{% for role in roles %}
        {{role.name}}: {get_attr: [{{role.name}}, attributes, os_collect_config]}
{% endfor %}

  DeployedServerEnvironment:
    type: OS::TripleO::DeployedServerEnvironment
    properties:
      RoleCounts:
{% for role in roles %}
        {{role.name}}DeployedServerCount: {get_param: {{role.name}}Count}
{% endfor %}
      VipMap:
        map_merge:
          - {get_attr: [VipMap, net_ip_map]}
          - redis: {get_attr: [RedisVirtualIP, ip_address]}
      DeployedServerPortMap:
        map_merge:
          list_concat:
{% for role in roles %}
              - {get_attr: [{{role.name}}, deployed_server_port_map]}
{% endfor %}
      DeployedServerDeploymentSwiftDataMap:
        map_merge:
          list_concat:
{% for role in roles %}
              - {get_attr: [{{role.name}}, deployed_server_deployment_swift_data_map]}
{% endfor %}
      DefaultRouteIp:
        str_split:
          - ':'
          - str_split:
            - '/'
            - {get_attr: [ServerOsCollectConfigData, value, {{primary_role_name}}, '0', request, metadata_url]}
            - 2
          - 0

outputs:
  ManagedEndpoints:
    description: Asserts that the keystone endpoints have been provisioned.
    value: true
  KeystoneURL:
    description: URL for the Overcloud Keystone service
    value: {get_attr: [EndpointMapData, value, KeystonePublic, uri]}
  KeystoneAdminVip:
    description: Keystone Admin VIP endpoint
    # Note that these nested get_attr functions require a dependency
    # relationship between VipMap and ServiceNetMap, since we can't determine
    # which attributes of VipMap are used until after ServiceNetMap's attribute
    # values are available. If this is ever reworked to not use nested
    # get_attr, that dependency can be removed.
    value: {get_attr: [VipMap, net_ip_map, {get_attr: [ServiceNetMap, service_net_map, KeystoneAdminApiNetwork]}]}
  EndpointMap:
    description: |
      Mapping of the resources with the needed info for their endpoints.
      This includes the protocol used, the IP, port and also a full
      representation of the URI.
    value: {get_attr: [EndpointMapData, value]}
  HostsEntry:
    description: |
      The content that should be appended to your /etc/hosts if you want to get
      hostname-based access to the deployed nodes (useful for testing without
      setting up a DNS).
    value:
      list_join:
      - "\n"
      - - {get_attr: [hostsConfig, hosts_entries]}
      - - {get_attr: [VipHosts, value]}
  EnabledServices:
    description: The services enabled on each role
    value:
{% for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}ServiceNames, value]}
{% endfor %}
  RoleData:
    description: The configuration data associated with each role
    value:
{% for role in roles %}
      {{role.name}}:
        map_merge:
        - {get_attr: [{{role.name}}ServiceChainRoleData, value]}
        - {get_attr: [{{role.name}}MergedConfigSettings, value]}
{% endfor %}
  RoleConfig:
    description: The configuration workflows associated with each role
    value: {get_attr: [AllNodesDeploySteps, RoleConfig]}
  RoleNetIpMap:
    description: Mapping of each network to a list of IPs for each role
    value:
{% for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}IpListMap, net_ip_map]}
{% endfor %}
  RoleNetHostnameMap:
    description: Mapping of each network to a list of hostnames for each role
    value:
{% for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}NetworkHostnameMap, value]}
{% endfor %}
  ServerOsCollectConfigData:
    description: The os-collect-config configuration associated with each server resource
    value: {get_attr: [ServerOsCollectConfigData, value]}
  VipMap:
    description: Mapping of each network to VIP addresses. Also includes the Redis VIP.
    value:
      map_merge:
        - {get_attr: [VipMap, net_ip_map]}
        - redis: {get_attr: [RedisVirtualIP, ip_address]}
  ServerIdData:
    description: Mapping of each role to a list of nova server IDs and the bootstrap ID
    value: {get_attr: [ServerIdMap, value]}
  DeployedServerEnvironment:
    description:
      Environment data that can be used as input into the services stack when
      using split-stack.
    value: {get_attr: [DeployedServerEnvironment, deployed_server_environment]}