Render VIPs dynamically based on network_data.yaml
[apex-tripleo-heat-templates.git] / overcloud.j2.yaml
1 {%- set primary_role = [roles[0]] -%}
2 {%- for role in roles -%}
3   {%- if 'primary' in role.tags and 'controller' in role.tags -%}
4     {%- set _ = primary_role.pop() -%}
5     {%- set _ = primary_role.append(role) -%}
6   {%- endif -%}
7 {%- endfor -%}
8 {%- set primary_role_name = primary_role[0].name -%}
9 # primary role is: {{primary_role_name}}
10 heat_template_version: pike
11
12 description: >
13   Deploy an OpenStack environment, consisting of several node types (roles),
14   Controller, Compute, BlockStorage, SwiftStorage and CephStorage. The Storage
15   roles enable independent scaling of the storage components, but the minimal
16   deployment is one Controller and one Compute node.
17
18
19 # TODO(shadower): we should probably use the parameter groups to put
20 # some order in here.
21 parameters:
22
23   # Common parameters (not specific to a role)
24 {%- for network in networks if network.vip|default(false) %}
25 {%- if network.name == 'External' %}
26   # Special case the External hostname param, which is CloudName
27   CloudName:
28     default: overcloud.localdomain
29     description: The DNS name of this cloud. E.g. ci-overcloud.tripleo.org
30     type: string
31 {%- elif network.name == 'InternalApi' %}
32   # Special case the Internal API hostname param, which is CloudNameInternal
33   CloudNameInternal:
34     default: overcloud.{{network.name.lower()}}.localdomain
35     description: >
36       The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
37       'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
38     type: string
39 {%- elif network.name == 'StorageMgmt' %}
40   # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
41   CloudNameStorageManagement:
42     default: overcloud.{{network.name.lower()}}.localdomain
43     description: >
44       The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
45       'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
46     type: string
47 {%- else %}
48   CloudName{{network.name}}:
49     default: overcloud.{{network.name.lower()}}.localdomain
50     description: >
51       The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
52       'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
53     type: string
54 {%- endif %}
55 {%- endfor %}
56   CloudNameCtlplane:
57     default: overcloud.ctlplane.localdomain
58     description: >
59       The DNS name of this cloud's provisioning network endpoint. E.g.
60       'ci-overcloud.ctlplane.tripleo.org'.
61     type: string
62   ExtraConfig:
63     default: {}
64     description: |
65       Additional hiera configuration to inject into the cluster.
66     type: json
67 {%- for role in roles %}
68   {{role.name}}ExtraConfig:
69     default: {}
70     description: |
71       Role specific additional hiera configuration to inject into the cluster.
72     type: json
73 {%- endfor %}
74   controllerExtraConfig:
75     default: {}
76     description: |
77       DEPRECATED use ControllerExtraConfig instead
78     type: json
79   NovaComputeExtraConfig:
80     default: {}
81     description: |
82       DEPRECATED use ComputeExtraConfig instead
83     type: json
84   NeutronControlPlaneID:
85     default: 'ctlplane'
86     type: string
87     description: Neutron ID or name for ctlplane network.
88   NeutronPublicInterface:
89     default: nic1
90     description: Which interface to add to the NeutronPhysicalBridge.
91     type: string
92   ControlFixedIPs:
93     default: []
94     description: >
95         Control the IP allocation for the ControlVirtualIP port. E.g.
96         [{'ip_address':'1.2.3.4'}]
97     type: json
98 {%- for network in networks if network.vip|default(false) %}
99 {%- if network.name == 'External' %}
100   # TODO (dsneddon) Legacy name, eventually refactor to match network name
101   PublicVirtualFixedIPs:
102     default: []
103     description: >
104         Control the IP allocation for the PublicVirtualInterface port. E.g.
105         [{'ip_address':'1.2.3.4'}]
106     type: json
107 {%- else %}
108   {{network.name}}VirtualFixedIPs:
109     default: []
110     description: >
111         Control the IP allocation for the {{network.name}}VirtualInterface port. E.g.
112         [{'ip_address':'1.2.3.4'}]
113     type: json
114 {%- endif %}
115 {%- endfor %}
116   RabbitCookieSalt:
117     type: string
118     default: unset
119     description: Salt for the rabbit cookie, change this to force the randomly generated rabbit cookie to change.
120   RedisVirtualFixedIPs:
121     default: []
122     description: >
123         Control the IP allocation for the virtual IP used by Redis. E.g.
124         [{'ip_address':'1.2.3.4'}]
125     type: json
126   CloudDomain:
127     default: 'localdomain'
128     type: string
129     description: >
130       The DNS domain used for the hosts. This must match the
131       overcloud_domain_name configured on the undercloud.
132   ServerMetadata:
133     default: {}
134     description: >
135       Extra properties or metadata passed to Nova for the created nodes in
136       the overcloud. It's accessible via the Nova metadata API.
137     type: json
138
139 # Compute-specific params
140 # FIXME(shardy) handle these deprecated names as they don't match compute.yaml
141   HypervisorNeutronPhysicalBridge:
142     default: 'br-ex'
143     description: >
144       An OVS bridge to create on each hypervisor. This defaults to br-ex the
145       same as the control plane nodes, as we have a uniform configuration of
146       the openvswitch agent. Typically should not need to be changed.
147     type: string
148   HypervisorNeutronPublicInterface:
149     default: nic1
150     description: What interface to add to the HypervisorNeutronPhysicalBridge.
151     type: string
152
153   NodeCreateBatchSize:
154     default: 30
155     description: Maxiumum batch size for creating nodes
156     type: number
157
158   # Jinja loop for Role in role_data.yaml
159 {% for role in roles %}
160   # Parameters generated for {{role.name}} Role
161   {{role.name}}Services:
162     description: A list of service resources (configured in the Heat
163                  resource_registry) which represent nested stacks
164                  for each service that should get installed on the {{role.name}} role.
165     type: comma_delimited_list
166
167   {{role.name}}Count:
168     description: Number of {{role.name}} nodes to deploy
169     type: number
170     default: {{role.CountDefault|default(0)}}
171
172   {{role.name}}HostnameFormat:
173     type: string
174     description: >
175       Format for {{role.name}} node hostnames
176       Note %index% is translated into the index of the node, e.g 0/1/2 etc
177       and %stackname% is replaced with the stack name e.g overcloud
178   {% if role.HostnameFormatDefault %}
179     default: "{{role.HostnameFormatDefault}}"
180   {% else %}
181     default: "%stackname%-{{role.name.lower()}}-%index%"
182   {% endif %}
183   {{role.name}}RemovalPolicies:
184     default: []
185     type: json
186     description: >
187       List of resources to be removed from {{role.name}} ResourceGroup when
188       doing an update which requires removal of specific resources.
189       Example format ComputeRemovalPolicies: [{'resource_list': ['0']}]
190
191 {% if role.name != 'Compute' %}
192   {{role.name}}SchedulerHints:
193 {% else %}
194   NovaComputeSchedulerHints:
195 {% endif %}
196     type: json
197     description: Optional scheduler hints to pass to nova
198     default: {}
199
200   {{role.name}}Parameters:
201     type: json
202     description: Optional Role Specific parameters to be provided to service
203     default: {}
204 {% endfor %}
205
206   # Identifiers to trigger tasks on nodes
207   UpdateIdentifier:
208     default: ''
209     type: string
210     description: >
211       Setting to a previously unused value during stack-update will trigger
212       package update on all nodes
213   DeployIdentifier:
214     default: ''
215     type: string
216     description: >
217       Setting this to a unique value will re-run any deployment tasks which
218       perform configuration on a Heat stack-update.
219   AddVipsToEtcHosts:
220     default: True
221     type: boolean
222     description: >
223       Set to true to append per network Vips to /etc/hosts on each node.
224
225   DeploymentServerBlacklist:
226     default: []
227     type: comma_delimited_list
228     description: >
229       List of server hostnames to blacklist from any triggered deployments.
230
231 parameter_groups:
232 - label: deprecated
233   description: Do not use deprecated params, they will be removed.
234   parameters:
235   - controllerExtraConfig
236   - NovaComputeExtraConfig
237
238 conditions:
239   add_vips_to_etc_hosts: {equals : [{get_param: AddVipsToEtcHosts}, True]}
240
241 resources:
242
243   VipHosts:
244     type: OS::Heat::Value
245     properties:
246       type: string
247       value:
248         list_join:
249         - "\n"
250         - - str_replace:
251               template: IP  HOST
252               params:
253                 IP: {get_attr: [VipMap, net_ip_map, ctlplane]}
254                 HOST: {get_param: CloudNameCtlplane}
255 {%- for network in networks if network.vip|default(false) %}
256 {%- if network.name == 'External' %}
257   # Special case the External hostname param, which is CloudName
258           - str_replace:
259               template: IP  HOST
260               params:
261                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
262                 HOST: {get_param: CloudName}
263 {%- elif network.name == 'InternalApi' %}
264   # Special case the Internal API hostname param, which is CloudNameInternal
265           - str_replace:
266               template: IP  HOST
267               params:
268                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
269                 HOST: {get_param: CloudNameInternal}
270 {%- elif network.name == 'StorageMgmt' %}
271   # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
272           - str_replace:
273               template: IP  HOST
274               params:
275                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
276                 HOST: {get_param: CloudNameStorageManagement}
277 {%- else %}
278           - str_replace:
279               template: IP  HOST
280               params:
281                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
282                 HOST: {get_param: CloudName{{network.name}}}
283 {%- endif %}
284 {%- endfor %}
285
286   HeatAuthEncryptionKey:
287     type: OS::TripleO::RandomString
288
289   PcsdPassword:
290     type: OS::TripleO::RandomString
291     properties:
292       length: 16
293
294   HorizonSecret:
295     type: OS::TripleO::RandomString
296     properties:
297       length: 10
298
299   NetCidrMapValue:
300     type: OS::Heat::Value
301     properties:
302       type: json
303       value:
304         map_replace:
305         - map_merge:
306           - {get_attr: [Networks, net_cidr_map]}
307           - ctlplane: {get_attr: [ControlVirtualIP, subnets, 0, cidr]}
308         - keys:
309             ctlplane: {get_param: NeutronControlPlaneID}
310           values:
311             disabled: {get_attr: [ControlVirtualIP, subnets, 0, cidr]}
312
313   ServiceNetMap:
314     type: OS::TripleO::ServiceNetMap
315
316   EndpointMap:
317     type: OS::TripleO::EndpointMap
318     properties:
319       CloudEndpoints:
320         ctlplane: {get_param: CloudNameCtlplane}
321 {%- for network in networks if network.vip|default(false) %}
322 {%- if network.name == 'External' %}
323   # Special case the External hostname param, which is CloudName
324         {{network.name_lower}}: {get_param: CloudName}
325 {%- elif network.name == 'InternalApi' %}
326   # Special case the Internal API hostname param, which is CloudNameInternal
327         {{network.name_lower}}: {get_param: CloudNameInternal}
328 {%- elif network.name == 'StorageMgmt' %}
329   # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
330         {{network.name_lower}}: {get_param: CloudNameStorageManagement}
331 {%- else %}
332         {{network.name_lower}}: {get_param: CloudName{{network.name}}}
333 {%- endif %}
334 {%- endfor %}
335       NetIpMap: {get_attr: [VipMap, net_ip_map]}
336       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
337
338   EndpointMapData:
339     type: OS::Heat::Value
340     properties:
341       type: json
342       value: {get_attr: [EndpointMap, endpoint_map]}
343
344   SshKnownHostsConfig:
345     type: OS::TripleO::Ssh::KnownHostsConfig
346     properties:
347       known_hosts:
348         list_join:
349           - ''
350           {% for role in roles %}
351           - {get_attr: [{{role.name}}, known_hosts_entry]}
352           {% endfor %}
353
354   # Jinja loop for Role in roles_data.yaml
355 {% for role in roles %}
356   # Resources generated for {{role.name}} Role
357   {{role.name}}ServiceChain:
358     type: OS::TripleO::Services
359     properties:
360       Services:
361         get_param: {{role.name}}Services
362       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
363       ServiceData:
364         net_cidr_map: {get_attr: [NetCidrMapValue, value]}
365       EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
366       DefaultPasswords: {get_attr: [DefaultPasswords, passwords]}
367       RoleName: {{role.name}}
368       RoleParameters: {get_param: {{role.name}}Parameters}
369
370   # Lookup of role_data via heat outputs is slow, so workaround this by caching
371   # the value in an OS::Heat::Value resource
372   {{role.name}}ServiceChainRoleData:
373     type: OS::Heat::Value
374     properties:
375       type: json
376       value: {get_attr: [{{role.name}}ServiceChain, role_data]}
377
378   {{role.name}}ServiceConfigSettings:
379     type: OS::Heat::Value
380     properties:
381       type: json
382       value:
383         map_merge:
384           - get_attr: [{{role.name}}ServiceChainRoleData, value, config_settings]
385           {% for r in roles %}
386           - get_attr: [{{r.name}}ServiceChainRoleData, value, global_config_settings]
387           {% endfor %}
388           # This next step combines two yaql passes:
389           # - The inner one does a deep merge on the service_config_settings for all roles
390           # - The outer one filters the map based on the services enabled for the role
391           #   then merges the result into one map.
392           - yaql:
393               expression: let(root => $) -> $.data.map.items().where($[0] in coalesce($root.data.services, [])).select($[1]).reduce($1.mergeWith($2), {})
394               data:
395                 map:
396                   yaql:
397                     expression: $.data.where($ != null).reduce($1.mergeWith($2), {})
398                     data:
399                     {% for r in roles %}
400                       - get_attr: [{{r.name}}ServiceChainRoleData, value, service_config_settings]
401                     {% endfor %}
402                 services: {get_attr: [{{role.name}}ServiceNames, value]}
403
404   {{role.name}}MergedConfigSettings:
405     type: OS::Heat::Value
406     properties:
407       type: json
408       value:
409         config_settings: {}
410         global_config_settings: {}
411         service_config_settings: {}
412         merged_config_settings:
413           map_merge:
414           - get_attr: [{{role.name}}ServiceConfigSettings, value]
415           - get_param: ExtraConfig
416           {%- if role.name == 'Controller' %}
417           - map_merge:
418             - get_param: controllerExtraConfig
419             - get_param: {{role.name}}ExtraConfig
420           {%- elif role.name == 'Compute' %}
421           - map_merge:
422             - get_param: NovaComputeExtraConfig
423             - get_param: {{role.name}}ExtraConfig
424           {%- else %}
425           - get_param: {{role.name}}ExtraConfig
426           {%- endif %}
427
428   # Filter any null/None service_names which may be present due to mapping
429   # of services to OS::Heat::None
430   {{role.name}}ServiceNames:
431     type: OS::Heat::Value
432     depends_on: {{role.name}}ServiceChain
433     properties:
434       type: comma_delimited_list
435       value:
436         yaql:
437           expression: coalesce($.data, []).where($ != null)
438           data: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_names]}
439
440   {{role.name}}HostsDeployment:
441     type: OS::Heat::StructuredDeployments
442     properties:
443       name: {{role.name}}HostsDeployment
444       config: {get_attr: [hostsConfig, config_id]}
445       servers: {get_attr: [{{role.name}}Servers, value]}
446
447   {{role.name}}SshKnownHostsDeployment:
448     type: OS::Heat::StructuredDeployments
449     properties:
450       name: {{role.name}}SshKnownHostsDeployment
451       config: {get_resource: SshKnownHostsConfig}
452       servers: {get_attr: [{{role.name}}Servers, value]}
453
454   {{role.name}}AllNodesDeployment:
455     type: OS::TripleO::AllNodesDeployment
456     depends_on:
457 {% for role_inner in roles %}
458       - {{role_inner.name}}HostsDeployment
459 {% endfor %}
460     properties:
461       name: {{role.name}}AllNodesDeployment
462       config: {get_attr: [allNodesConfig, config_id]}
463       servers: {get_attr: [{{role.name}}Servers, value]}
464       input_values:
465         # Note we have to use yaql to look up the first hostname/ip in the
466         # list because heat path based attributes operate on the attribute
467         # inside the ResourceGroup, not the exposed list ref discussion in
468         # https://bugs.launchpad.net/heat/+bug/1640488
469         # The coalesce is needed because $.data is None during heat validation
470         bootstrap_nodeid:
471           yaql:
472             expression: coalesce($.data, []).first(null)
473             data: {get_attr: [{{role.name}}, hostname]}
474         bootstrap_nodeid_ip:
475           yaql:
476             expression: coalesce($.data, []).first(null)
477             data: {get_attr: [{{role.name}}, ip_address]}
478
479   {{role.name}}AllNodesValidationDeployment:
480     type: OS::Heat::StructuredDeployments
481     depends_on: {{role.name}}AllNodesDeployment
482     properties:
483       name: {{role.name}}AllNodesValidationDeployment
484       config: {get_resource: AllNodesValidationConfig}
485       servers: {get_attr: [{{role.name}}Servers, value]}
486
487   {{role.name}}IpListMap:
488     type: OS::TripleO::Network::Ports::NetIpListMap
489     properties:
490       ControlPlaneIpList: {get_attr: [{{role.name}}, ip_address]}
491 {%- for network in networks if network.enabled|default(true) %}
492       {{network.name}}IpList: {get_attr: [{{role.name}}, {{network.name_lower}}_ip_address]}
493 {%- endfor %}
494       EnabledServices: {get_attr: [{{role.name}}ServiceNames, value]}
495       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
496       ServiceHostnameList: {get_attr: [{{role.name}}, hostname]}
497       NetworkHostnameMap: {get_attr: [{{role.name}}NetworkHostnameMap, value]}
498
499   {{role.name}}NetworkHostnameMap:
500     type: OS::Heat::Value
501     properties:
502       type: json
503       value:
504         # Note (shardy) this somewhat complex yaql may be replaced
505         # with a map_deep_merge function in ocata.  It merges the
506         # list of maps, but appends to colliding lists so we can
507         # create a map of lists for all nodes for each network
508         yaql:
509           expression: dict($.data.where($ != null).flatten().selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
510           data:
511             - {get_attr: [{{role.name}}, hostname_map]}
512
513   {{role.name}}:
514     type: OS::Heat::ResourceGroup
515     depends_on: Networks
516     update_policy:
517       batch_create:
518         max_batch_size: {get_param: NodeCreateBatchSize}
519     properties:
520       count: {get_param: {{role.name}}Count}
521       removal_policies: {get_param: {{role.name}}RemovalPolicies}
522       resource_def:
523         type: OS::TripleO::{{role.name}}
524         properties:
525           CloudDomain: {get_param: CloudDomain}
526           ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
527           EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
528           Hostname:
529             str_replace:
530               template: {get_param: {{role.name}}HostnameFormat}
531               params:
532                 '%stackname%': {get_param: 'OS::stack_name'}
533           NodeIndex: '%index%'
534   {% if role.name != 'Compute' %}
535           {{role.name}}SchedulerHints: {get_param: {{role.name}}SchedulerHints}
536   {% else %}
537           NovaComputeSchedulerHints: {get_param: NovaComputeSchedulerHints}
538   {% endif %}
539           ServiceConfigSettings: {get_attr: [{{role.name}}ServiceConfigSettings, value]}
540           ServiceNames: {get_attr: [{{role.name}}ServiceNames, value]}
541           MonitoringSubscriptions: {get_attr: [{{role.name}}ServiceChainRoleData, value, monitoring_subscriptions]}
542           ServiceMetadataSettings: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_metadata_settings]}
543           DeploymentServerBlacklistDict: {get_attr: [DeploymentServerBlacklistDict, value]}
544           RoleParameters: {get_param: {{role.name}}Parameters}
545 {% endfor %}
546
547 {% for role in roles %}
548   {{role.name}}Servers:
549     type: OS::Heat::Value
550     depends_on: {{role.name}}
551     properties:
552       type: json
553       value:
554         yaql:
555           expression: let(servers=>switch(isDict($.data.servers) => $.data.servers, true => {})) -> $servers.deleteAll($servers.keys().where($servers[$] = null))
556           data:
557             servers: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
558 {% endfor %}
559
560   # This is a different format to *Servers, as it creates a map of lists
561   # whereas *Servers creates a map of maps with keys of the nested resource names
562   ServerIdMap:
563     type: OS::Heat::Value
564     properties:
565       value:
566         server_ids:
567 {% for role in roles %}
568           {{role.name}}: {get_attr: [{{role.name}}, nova_server_resource]}
569 {% endfor %}
570         bootstrap_server_id:
571           yaql:
572             expression: coalesce($.data, []).first(null)
573             data: {get_attr: [{{primary_role_name}}, nova_server_resource]}
574
575   # This resource just creates a dict out of the DeploymentServerBlacklist,
576   # which is a list. The dict is used in the role templates to set a condition
577   # on whether to create the deployment resources. We can't use the list
578   # directly because there is no way to ask Heat if a list contains a specific
579   # value.
580   DeploymentServerBlacklistDict:
581     type: OS::Heat::Value
582     properties:
583       type: json
584       value:
585         map_merge:
586           repeat:
587             template:
588               hostname: 1
589             for_each:
590               hostname: {get_param: DeploymentServerBlacklist}
591
592   hostsConfig:
593     type: OS::TripleO::Hosts::SoftwareConfig
594     properties:
595       hosts:
596         list_join:
597         - "\n"
598         - - if:
599             - add_vips_to_etc_hosts
600             - {get_attr: [VipHosts, value]}
601             - ''
602         -
603 {% for role in roles %}
604           - list_join:
605             - ""
606             - {get_attr: [{{role.name}}, hosts_entry]}
607 {% endfor %}
608
609   allNodesConfig:
610     type: OS::TripleO::AllNodes::SoftwareConfig
611     properties:
612 {%- for network in networks if network.vip|default(false) %}
613 {%- if network.name == 'External' %}
614   # Special case the External hostname param, which is CloudName
615       cloud_name_{{network.name_lower}}: {get_param: CloudName}
616 {%- elif network.name == 'InternalApi' %}
617   # Special case the Internal API hostname param, which is CloudNameInternal
618       cloud_name_{{network.name_lower}}: {get_param: CloudNameInternal}
619 {%- elif network.name == 'StorageMgmt' %}
620   # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
621       cloud_name_{{network.name_lower}}: {get_param: CloudNameStorageManagement}
622 {%- else %}
623       cloud_name_{{network.name_lower}}: {get_param: CloudName{{network.name}}}
624 {%- endif %}
625 {%- endfor %}
626       cloud_name_ctlplane: {get_param: CloudNameCtlplane}
627       enabled_services:
628         list_join:
629           - ','
630 {% for role in roles %}
631           - {get_attr: [{{role.name}}ServiceNames, value]}
632 {% endfor %}
633       logging_groups:
634         yaql:
635           expression: >
636             $.data.groups.flatten()
637           data:
638             groups:
639 {% for role in roles %}
640               - {get_attr: [{{role.name}}ServiceChainRoleData, value, logging_groups]}
641 {% endfor %}
642       logging_sources:
643         yaql:
644           expression: >
645             $.data.sources.flatten()
646           data:
647             sources:
648 {% for role in roles %}
649               - {get_attr: [{{role.name}}ServiceChainRoleData, value, logging_sources]}
650 {% endfor %}
651       controller_ips: {get_attr: [{{primary_role_name}}, ip_address]}
652       controller_names: {get_attr: [{{primary_role_name}}, hostname]}
653       service_ips:
654         # Note (shardy) this somewhat complex yaql may be replaced
655         # with a map_deep_merge function in ocata.  It merges the
656         # list of maps, but appends to colliding lists when a service
657         # is deployed on more than one role
658         yaql:
659           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
660           data:
661             l:
662 {% for role in roles %}
663               - {get_attr: [{{role.name}}IpListMap, service_ips]}
664 {% endfor %}
665       service_node_names:
666         yaql:
667           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
668           data:
669             l:
670 {% for role in roles %}
671               - {get_attr: [{{role.name}}IpListMap, service_hostnames]}
672 {% endfor %}
673       short_service_node_names:
674         yaql:
675           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
676           data:
677             l:
678 {% for role in roles %}
679               - {get_attr: [{{role.name}}IpListMap, short_service_hostnames]}
680 {% endfor %}
681       short_service_bootstrap_node:
682         yaql:
683           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten().first()]))
684           data:
685             l:
686 {% for role in roles %}
687               - {get_attr: [{{role.name}}IpListMap, short_service_bootstrap_hostnames]}
688 {% endfor %}
689       NetVipMap: {get_attr: [VipMap, net_ip_map]}
690       RedisVirtualIP: {get_attr: [RedisVirtualIP, ip_address]}
691       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
692       DeployIdentifier: {get_param: DeployIdentifier}
693       UpdateIdentifier: {get_param: UpdateIdentifier}
694
695   MysqlRootPassword:
696     type: OS::TripleO::RandomString
697     properties:
698       length: 10
699
700   RabbitCookie:
701     type: OS::TripleO::RandomString
702     properties:
703       length: 20
704       salt: {get_param: RabbitCookieSalt}
705
706   DefaultPasswords:
707     type: OS::TripleO::DefaultPasswords
708     properties:
709       DefaultMysqlRootPassword: {get_attr: [MysqlRootPassword, value]}
710       DefaultRabbitCookie: {get_attr: [RabbitCookie, value]}
711       DefaultHeatAuthEncryptionKey: {get_attr: [HeatAuthEncryptionKey, value]}
712       DefaultPcsdPassword: {get_attr: [PcsdPassword, value]}
713       DefaultHorizonSecret: {get_attr: [HorizonSecret, value]}
714
715   # creates the network architecture
716   Networks:
717     type: OS::TripleO::Network
718
719   ControlVirtualIP:
720     type: OS::TripleO::Network::Ports::ControlPlaneVipPort
721     depends_on: Networks
722     properties:
723       name: control_virtual_ip
724       network: {get_param: NeutronControlPlaneID}
725       fixed_ips: {get_param: ControlFixedIPs}
726       replacement_policy: AUTO
727
728   RedisVirtualIP:
729     depends_on: Networks
730     type: OS::TripleO::Network::Ports::RedisVipPort
731     properties:
732       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
733       ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
734       PortName: redis_virtual_ip
735       NetworkName: {get_attr: [ServiceNetMap, service_net_map, RedisNetwork]}
736       ServiceName: redis
737       FixedIPs: {get_param: RedisVirtualFixedIPs}
738
739 {%- for network in networks if network.vip|default(false) %}
740 {%- if network.name == 'External' %}
741   # The public VIP is on the External net, falls back to ctlplane
742   PublicVirtualIP:
743     depends_on: Networks
744     type: OS::TripleO::Network::Ports::ExternalVipPort
745     properties:
746       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
747       ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
748       PortName: public_virtual_ip
749       FixedIPs: {get_param: PublicVirtualFixedIPs}
750 {%- elif network.name == 'StorageMgmt' %}
751   {{network.name}}VirtualIP:
752     depends_on: Networks
753     type: OS::TripleO::Network::Ports::{{network.name}}VipPort
754     properties:
755       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
756       PortName: storage_management_virtual_ip
757       FixedIPs: {get_param: {{network.name}}VirtualFixedIPs}
758 {%- else %}
759   {{network.name}}VirtualIP:
760     depends_on: Networks
761     type: OS::TripleO::Network::Ports::{{network.name}}VipPort
762     properties:
763       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
764       PortName: {{network.name_lower}}_virtual_ip
765       FixedIPs: {get_param: {{network.name}}VirtualFixedIPs}
766 {%- endif %}
767 {%- endfor %}
768
769   VipMap:
770     type: OS::TripleO::Network::Ports::NetVipMap
771     properties:
772       ControlPlaneIp: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
773 {%- for network in networks if network.vip|default(false) %}
774 {%- if network.name == 'External' %}
775       ExternalIp: {get_attr: [PublicVirtualIP, ip_address]}
776       ExternalIpUri: {get_attr: [PublicVirtualIP, ip_address_uri]}
777 {%- else %}
778       {{network.name}}Ip: {get_attr: [{{network.name}}VirtualIP, ip_address]}
779       {{network.name}}IpUri: {get_attr: [{{network.name}}VirtualIP, ip_address_uri]}
780 {%- endif %}
781 {%- endfor %}
782       # No tenant or management VIP required
783     # Because of nested get_attr functions in the KeystoneAdminVip output, we
784     # can't determine which attributes of VipMap are used until after
785     # ServiceNetMap's attribute values are available.
786     depends_on: ServiceNetMap
787
788   # All Nodes Validations
789   AllNodesValidationConfig:
790     type: OS::TripleO::AllNodes::Validation
791     properties:
792       PingTestIps:
793         list_join:
794         - ' '
795         -
796 {%- for network in networks if network.enabled|default(true) %}
797           - yaql:
798               expression: coalesce($.data, []).first(null)
799               data: {get_attr: [{{primary_role_name}}, {{network.name_lower}}_ip_address]}
800 {%- endfor %}
801
802   UpdateWorkflow:
803     type: OS::TripleO::Tasks::UpdateWorkflow
804     depends_on:
805 {% for role in roles %}
806       - {{role.name}}AllNodesDeployment
807 {% endfor %}
808     properties:
809       servers:
810 {% for role in roles %}
811         {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
812 {% endfor %}
813       input_values:
814         deploy_identifier: {get_param: DeployIdentifier}
815         update_identifier: {get_param: UpdateIdentifier}
816
817   # Optional ExtraConfig for all nodes - all roles are passed in here, but
818   # the nested template may configure each role differently (or not at all)
819   AllNodesExtraConfig:
820     type: OS::TripleO::AllNodesExtraConfig
821     depends_on:
822       - UpdateWorkflow
823 {% for role in roles %}
824       - {{role.name}}AllNodesValidationDeployment
825 {% endfor %}
826     properties:
827       servers:
828 {% for role in roles %}
829         {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
830 {% endfor %}
831
832   # Post deployment steps for all roles
833   AllNodesDeploySteps:
834     type: OS::TripleO::PostDeploySteps
835     depends_on:
836       - AllNodesExtraConfig
837 {% for role in roles %}
838       - {{role.name}}AllNodesDeployment
839 {% endfor %}
840     properties:
841       servers:
842 {% for role in roles %}
843         {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
844 {% endfor %}
845       stack_name: {get_param: 'OS::stack_name'}
846       EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
847       ctlplane_service_ips:
848         # Note (shardy) this somewhat complex yaql may be replaced
849         # with a map_deep_merge function in ocata.  It merges the
850         # list of maps, but appends to colliding lists when a service
851         # is deployed on more than one role
852         yaql:
853           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
854           data:
855             l:
856 {% for role in roles %}
857               - {get_attr: [{{role.name}}IpListMap, ctlplane_service_ips]}
858 {% endfor %}
859       role_data:
860 {% for role in roles %}
861         {{role.name}}:
862           map_merge:
863           - {get_attr: [{{role.name}}ServiceChainRoleData, value]}
864           - {get_attr: [{{role.name}}MergedConfigSettings, value]}
865 {% endfor %}
866
867   ServerOsCollectConfigData:
868     type: OS::Heat::Value
869     properties:
870       type: json
871       value:
872 {% for role in roles %}
873         {{role.name}}: {get_attr: [{{role.name}}, attributes, os_collect_config]}
874 {% endfor %}
875
876   DeployedServerEnvironment:
877     type: OS::TripleO::DeployedServerEnvironment
878     properties:
879       RoleCounts:
880 {% for role in roles %}
881         {{role.name}}DeployedServerCount: {get_param: {{role.name}}Count}
882 {% endfor %}
883       VipMap:
884         map_merge:
885           - {get_attr: [VipMap, net_ip_map]}
886           - redis: {get_attr: [RedisVirtualIP, ip_address]}
887       DeployedServerPortMap:
888         map_merge:
889           list_concat:
890 {% for role in roles %}
891               - {get_attr: [{{role.name}}, deployed_server_port_map]}
892 {% endfor %}
893       DeployedServerDeploymentSwiftDataMap:
894         map_merge:
895           list_concat:
896 {% for role in roles %}
897               - {get_attr: [{{role.name}}, deployed_server_deployment_swift_data_map]}
898 {% endfor %}
899       DefaultRouteIp:
900         str_split:
901           - ':'
902           - str_split:
903             - '/'
904             - {get_attr: [ServerOsCollectConfigData, value, {{primary_role_name}}, '0', request, metadata_url]}
905             - 2
906           - 0
907
908 outputs:
909   ManagedEndpoints:
910     description: Asserts that the keystone endpoints have been provisioned.
911     value: true
912   KeystoneURL:
913     description: URL for the Overcloud Keystone service
914     value: {get_attr: [EndpointMapData, value, KeystonePublic, uri]}
915   KeystoneAdminVip:
916     description: Keystone Admin VIP endpoint
917     # Note that these nested get_attr functions require a dependency
918     # relationship between VipMap and ServiceNetMap, since we can't determine
919     # which attributes of VipMap are used until after ServiceNetMap's attribute
920     # values are available. If this is ever reworked to not use nested
921     # get_attr, that dependency can be removed.
922     value: {get_attr: [VipMap, net_ip_map, {get_attr: [ServiceNetMap, service_net_map, KeystoneAdminApiNetwork]}]}
923   EndpointMap:
924     description: |
925       Mapping of the resources with the needed info for their endpoints.
926       This includes the protocol used, the IP, port and also a full
927       representation of the URI.
928     value: {get_attr: [EndpointMapData, value]}
929   HostsEntry:
930     description: |
931       The content that should be appended to your /etc/hosts if you want to get
932       hostname-based access to the deployed nodes (useful for testing without
933       setting up a DNS).
934     value:
935       list_join:
936       - "\n"
937       - - {get_attr: [hostsConfig, hosts_entries]}
938       - - {get_attr: [VipHosts, value]}
939   EnabledServices:
940     description: The services enabled on each role
941     value:
942 {% for role in roles %}
943       {{role.name}}: {get_attr: [{{role.name}}ServiceNames, value]}
944 {% endfor %}
945   RoleData:
946     description: The configuration data associated with each role
947     value:
948 {% for role in roles %}
949       {{role.name}}:
950         map_merge:
951         - {get_attr: [{{role.name}}ServiceChainRoleData, value]}
952         - {get_attr: [{{role.name}}MergedConfigSettings, value]}
953 {% endfor %}
954   RoleNetIpMap:
955     description: Mapping of each network to a list of IPs for each role
956     value:
957 {% for role in roles %}
958       {{role.name}}: {get_attr: [{{role.name}}IpListMap, net_ip_map]}
959 {% endfor %}
960   RoleNetHostnameMap:
961     description: Mapping of each network to a list of hostnames for each role
962     value:
963 {% for role in roles %}
964       {{role.name}}: {get_attr: [{{role.name}}NetworkHostnameMap, value]}
965 {% endfor %}
966   ServerOsCollectConfigData:
967     description: The os-collect-config configuration associated with each server resource
968     value: {get_attr: [ServerOsCollectConfigData, value]}
969   VipMap:
970     description: Mapping of each network to VIP addresses. Also includes the Redis VIP.
971     value:
972       map_merge:
973         - {get_attr: [VipMap, net_ip_map]}
974         - redis: {get_attr: [RedisVirtualIP, ip_address]}
975   ServerIdData:
976     description: Mapping of each role to a list of nova server IDs and the bootstrap ID
977     value: {get_attr: [ServerIdMap, value]}
978   DeployedServerEnvironment:
979     description:
980       Environment data that can be used as input into the services stack when
981       using split-stack.
982     value: {get_attr: [DeployedServerEnvironment, deployed_server_environment]}