Merge "Enable TLS for containerized haproxy"
[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     description: Optional scheduler hints to pass to nova
194 {% else %}
195   NovaComputeSchedulerHints:
196     description: DEPRECATED - use ComputeSchedulerHints instead
197 {% endif %}
198     type: json
199     default: {}
200
201   {{role.name}}Parameters:
202     type: json
203     description: Optional Role Specific parameters to be provided to service
204     default: {}
205 {% endfor %}
206
207   # Identifiers to trigger tasks on nodes
208   UpdateIdentifier:
209     default: ''
210     type: string
211     description: >
212       Setting to a previously unused value during stack-update will trigger
213       package update on all nodes
214   DeployIdentifier:
215     default: ''
216     type: string
217     description: >
218       Setting this to a unique value will re-run any deployment tasks which
219       perform configuration on a Heat stack-update.
220   AddVipsToEtcHosts:
221     default: True
222     type: boolean
223     description: >
224       Set to true to append per network Vips to /etc/hosts on each node.
225
226   DeploymentServerBlacklist:
227     default: []
228     type: comma_delimited_list
229     description: >
230       List of server hostnames to blacklist from any triggered deployments.
231
232 conditions:
233   add_vips_to_etc_hosts: {equals : [{get_param: AddVipsToEtcHosts}, True]}
234
235 resources:
236
237   VipHosts:
238     type: OS::Heat::Value
239     properties:
240       type: string
241       value:
242         list_join:
243         - "\n"
244         - - str_replace:
245               template: IP  HOST
246               params:
247                 IP: {get_attr: [VipMap, net_ip_map, ctlplane]}
248                 HOST: {get_param: CloudNameCtlplane}
249 {%- for network in networks if network.vip|default(false) %}
250 {%- if network.name == 'External' %}
251   # Special case the External hostname param, which is CloudName
252           - str_replace:
253               template: IP  HOST
254               params:
255                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
256                 HOST: {get_param: CloudName}
257 {%- elif network.name == 'InternalApi' %}
258   # Special case the Internal API hostname param, which is CloudNameInternal
259           - str_replace:
260               template: IP  HOST
261               params:
262                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
263                 HOST: {get_param: CloudNameInternal}
264 {%- elif network.name == 'StorageMgmt' %}
265   # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
266           - str_replace:
267               template: IP  HOST
268               params:
269                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
270                 HOST: {get_param: CloudNameStorageManagement}
271 {%- else %}
272           - str_replace:
273               template: IP  HOST
274               params:
275                 IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
276                 HOST: {get_param: CloudName{{network.name}}}
277 {%- endif %}
278 {%- endfor %}
279
280   HeatAuthEncryptionKey:
281     type: OS::TripleO::RandomString
282
283   PcsdPassword:
284     type: OS::TripleO::RandomString
285     properties:
286       length: 16
287
288   HorizonSecret:
289     type: OS::TripleO::RandomString
290     properties:
291       length: 10
292
293   NetCidrMapValue:
294     type: OS::Heat::Value
295     properties:
296       type: json
297       value:
298         map_replace:
299         - map_merge:
300           - {get_attr: [Networks, net_cidr_map]}
301           - ctlplane: {get_attr: [ControlVirtualIP, subnets, 0, cidr]}
302         - keys:
303             ctlplane: {get_param: NeutronControlPlaneID}
304           values:
305             disabled: {get_attr: [ControlVirtualIP, subnets, 0, cidr]}
306
307   ServiceNetMap:
308     type: OS::TripleO::ServiceNetMap
309
310   EndpointMap:
311     type: OS::TripleO::EndpointMap
312     properties:
313       CloudEndpoints:
314         ctlplane: {get_param: CloudNameCtlplane}
315 {%- for network in networks if network.vip|default(false) %}
316 {%- if network.name == 'External' %}
317   # Special case the External hostname param, which is CloudName
318         {{network.name_lower}}: {get_param: CloudName}
319 {%- elif network.name == 'InternalApi' %}
320   # Special case the Internal API hostname param, which is CloudNameInternal
321         {{network.name_lower}}: {get_param: CloudNameInternal}
322 {%- elif network.name == 'StorageMgmt' %}
323   # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
324         {{network.name_lower}}: {get_param: CloudNameStorageManagement}
325 {%- else %}
326         {{network.name_lower}}: {get_param: CloudName{{network.name}}}
327 {%- endif %}
328 {%- endfor %}
329       NetIpMap: {get_attr: [VipMap, net_ip_map]}
330       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
331
332   EndpointMapData:
333     type: OS::Heat::Value
334     properties:
335       type: json
336       value: {get_attr: [EndpointMap, endpoint_map]}
337
338   SshKnownHostsConfig:
339     type: OS::TripleO::Ssh::KnownHostsConfig
340     properties:
341       known_hosts:
342         list_join:
343           - ''
344           {% for role in roles %}
345           - {get_attr: [{{role.name}}, known_hosts_entry]}
346           {% endfor %}
347
348   # Jinja loop for Role in roles_data.yaml
349 {% for role in roles %}
350   # Resources generated for {{role.name}} Role
351   {{role.name}}ServiceChain:
352     type: OS::TripleO::Services
353     properties:
354       Services:
355         get_param: {{role.name}}Services
356       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
357       ServiceData:
358         net_cidr_map: {get_attr: [NetCidrMapValue, value]}
359       EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
360       DefaultPasswords: {get_attr: [DefaultPasswords, passwords]}
361       RoleName: {{role.name}}
362       RoleParameters: {get_param: {{role.name}}Parameters}
363
364   # Lookup of role_data via heat outputs is slow, so workaround this by caching
365   # the value in an OS::Heat::Value resource
366   {{role.name}}ServiceChainRoleData:
367     type: OS::Heat::Value
368     properties:
369       type: json
370       value: {get_attr: [{{role.name}}ServiceChain, role_data]}
371
372   {{role.name}}ServiceConfigSettings:
373     type: OS::Heat::Value
374     properties:
375       type: json
376       value:
377         map_merge:
378           - get_attr: [{{role.name}}ServiceChainRoleData, value, config_settings]
379           {% for r in roles %}
380           - get_attr: [{{r.name}}ServiceChainRoleData, value, global_config_settings]
381           {% endfor %}
382           # This next step combines two yaql passes:
383           # - The inner one does a deep merge on the service_config_settings for all roles
384           # - The outer one filters the map based on the services enabled for the role
385           #   then merges the result into one map.
386           - yaql:
387               expression: let(root => $) -> $.data.map.items().where($[0] in coalesce($root.data.services, [])).select($[1]).reduce($1.mergeWith($2), {})
388               data:
389                 map:
390                   yaql:
391                     expression: $.data.where($ != null).reduce($1.mergeWith($2), {})
392                     data:
393                     {% for r in roles %}
394                       - get_attr: [{{r.name}}ServiceChainRoleData, value, service_config_settings]
395                     {% endfor %}
396                 services: {get_attr: [{{role.name}}ServiceNames, value]}
397
398   {{role.name}}MergedConfigSettings:
399     type: OS::Heat::Value
400     properties:
401       type: json
402       value:
403         config_settings: {}
404         global_config_settings: {}
405         service_config_settings: {}
406         merged_config_settings:
407           map_merge:
408           - get_attr: [{{role.name}}ServiceConfigSettings, value]
409           - get_param: ExtraConfig
410           {%- if role.name == 'Controller' %}
411           - map_merge:
412             - get_param: controllerExtraConfig
413             - get_param: {{role.name}}ExtraConfig
414           {%- elif role.name == 'Compute' %}
415           - map_merge:
416             - get_param: NovaComputeExtraConfig
417             - get_param: {{role.name}}ExtraConfig
418           {%- else %}
419           - get_param: {{role.name}}ExtraConfig
420           {%- endif %}
421
422   # Filter any null/None service_names which may be present due to mapping
423   # of services to OS::Heat::None
424   {{role.name}}ServiceNames:
425     type: OS::Heat::Value
426     depends_on: {{role.name}}ServiceChain
427     properties:
428       type: comma_delimited_list
429       value:
430         yaql:
431           expression: coalesce($.data, []).where($ != null)
432           data: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_names]}
433
434   {{role.name}}HostsDeployment:
435     type: OS::Heat::StructuredDeployments
436     properties:
437       name: {{role.name}}HostsDeployment
438       config: {get_attr: [hostsConfig, config_id]}
439       servers: {get_attr: [{{role.name}}Servers, value]}
440
441   {{role.name}}SshKnownHostsDeployment:
442     type: OS::Heat::StructuredDeployments
443     properties:
444       name: {{role.name}}SshKnownHostsDeployment
445       config: {get_resource: SshKnownHostsConfig}
446       servers: {get_attr: [{{role.name}}Servers, value]}
447
448   {{role.name}}AllNodesDeployment:
449     type: OS::TripleO::AllNodesDeployment
450     depends_on:
451 {% for role_inner in roles %}
452       - {{role_inner.name}}HostsDeployment
453 {% endfor %}
454     properties:
455       name: {{role.name}}AllNodesDeployment
456       config: {get_attr: [allNodesConfig, config_id]}
457       servers: {get_attr: [{{role.name}}Servers, value]}
458       input_values:
459         # Note we have to use yaql to look up the first hostname/ip in the
460         # list because heat path based attributes operate on the attribute
461         # inside the ResourceGroup, not the exposed list ref discussion in
462         # https://bugs.launchpad.net/heat/+bug/1640488
463         # The coalesce is needed because $.data is None during heat validation
464         bootstrap_nodeid:
465           yaql:
466             expression: coalesce($.data, []).first(null)
467             data: {get_attr: [{{role.name}}, hostname]}
468         bootstrap_nodeid_ip:
469           yaql:
470             expression: coalesce($.data, []).first(null)
471             data: {get_attr: [{{role.name}}, ip_address]}
472
473   {{role.name}}AllNodesValidationDeployment:
474     type: OS::Heat::StructuredDeployments
475     depends_on: {{role.name}}AllNodesDeployment
476     properties:
477       name: {{role.name}}AllNodesValidationDeployment
478       config: {get_resource: AllNodesValidationConfig}
479       servers: {get_attr: [{{role.name}}Servers, value]}
480
481   {{role.name}}IpListMap:
482     type: OS::TripleO::Network::Ports::NetIpListMap
483     properties:
484       ControlPlaneIpList: {get_attr: [{{role.name}}, ip_address]}
485 {%- for network in networks if network.enabled|default(true) %}
486       {{network.name}}IpList: {get_attr: [{{role.name}}, {{network.name_lower}}_ip_address]}
487 {%- endfor %}
488       EnabledServices: {get_attr: [{{role.name}}ServiceNames, value]}
489       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
490       ServiceHostnameList: {get_attr: [{{role.name}}, hostname]}
491       NetworkHostnameMap: {get_attr: [{{role.name}}NetworkHostnameMap, value]}
492
493   {{role.name}}NetworkHostnameMap:
494     type: OS::Heat::Value
495     properties:
496       type: json
497       value:
498         # Note (shardy) this somewhat complex yaql may be replaced
499         # with a map_deep_merge function in ocata.  It merges the
500         # list of maps, but appends to colliding lists so we can
501         # create a map of lists for all nodes for each network
502         yaql:
503           expression: dict($.data.where($ != null).flatten().selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
504           data:
505             - {get_attr: [{{role.name}}, hostname_map]}
506
507   {{role.name}}:
508     type: OS::Heat::ResourceGroup
509     depends_on: Networks
510     update_policy:
511       batch_create:
512         max_batch_size: {get_param: NodeCreateBatchSize}
513     properties:
514       count: {get_param: {{role.name}}Count}
515       removal_policies: {get_param: {{role.name}}RemovalPolicies}
516       resource_def:
517         type: OS::TripleO::{{role.name}}
518         properties:
519           CloudDomain: {get_param: CloudDomain}
520           ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
521           EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
522           Hostname:
523             str_replace:
524               template: {get_param: {{role.name}}HostnameFormat}
525               params:
526                 '%stackname%': {get_param: 'OS::stack_name'}
527           NodeIndex: '%index%'
528   {% if role.name != 'Compute' %}
529           {{role.name}}SchedulerHints: {get_param: {{role.name}}SchedulerHints}
530   {% else %}
531           NovaComputeSchedulerHints: {get_param: NovaComputeSchedulerHints}
532   {% endif %}
533           ServiceConfigSettings: {get_attr: [{{role.name}}ServiceConfigSettings, value]}
534           ServiceNames: {get_attr: [{{role.name}}ServiceNames, value]}
535           MonitoringSubscriptions: {get_attr: [{{role.name}}ServiceChainRoleData, value, monitoring_subscriptions]}
536           ServiceMetadataSettings: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_metadata_settings]}
537           DeploymentServerBlacklistDict: {get_attr: [DeploymentServerBlacklistDict, value]}
538           RoleParameters: {get_param: {{role.name}}Parameters}
539 {% endfor %}
540
541 {% for role in roles %}
542   {{role.name}}Servers:
543     type: OS::Heat::Value
544     depends_on: {{role.name}}
545     properties:
546       type: json
547       value:
548         yaql:
549           expression: let(servers=>switch(isDict($.data.servers) => $.data.servers, true => {})) -> $servers.deleteAll($servers.keys().where($servers[$] = null))
550           data:
551             servers: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
552 {% endfor %}
553
554   # This is a different format to *Servers, as it creates a map of lists
555   # whereas *Servers creates a map of maps with keys of the nested resource names
556   ServerIdMap:
557     type: OS::Heat::Value
558     properties:
559       value:
560         server_ids:
561 {% for role in roles %}
562           {{role.name}}: {get_attr: [{{role.name}}, nova_server_resource]}
563 {% endfor %}
564         bootstrap_server_id:
565           yaql:
566             expression: coalesce($.data, []).first(null)
567             data: {get_attr: [{{primary_role_name}}, nova_server_resource]}
568
569   # This resource just creates a dict out of the DeploymentServerBlacklist,
570   # which is a list. The dict is used in the role templates to set a condition
571   # on whether to create the deployment resources. We can't use the list
572   # directly because there is no way to ask Heat if a list contains a specific
573   # value.
574   DeploymentServerBlacklistDict:
575     type: OS::Heat::Value
576     properties:
577       type: json
578       value:
579         map_merge:
580           repeat:
581             template:
582               hostname: 1
583             for_each:
584               hostname: {get_param: DeploymentServerBlacklist}
585
586   hostsConfig:
587     type: OS::TripleO::Hosts::SoftwareConfig
588     properties:
589       hosts:
590         list_join:
591         - "\n"
592         - - if:
593             - add_vips_to_etc_hosts
594             - {get_attr: [VipHosts, value]}
595             - ''
596         -
597 {% for role in roles %}
598           - list_join:
599             - ""
600             - {get_attr: [{{role.name}}, hosts_entry]}
601 {% endfor %}
602
603   allNodesConfig:
604     type: OS::TripleO::AllNodes::SoftwareConfig
605     properties:
606 {%- for network in networks if network.vip|default(false) %}
607 {%- if network.name == 'External' %}
608   # Special case the External hostname param, which is CloudName
609       cloud_name_{{network.name_lower}}: {get_param: CloudName}
610 {%- elif network.name == 'InternalApi' %}
611   # Special case the Internal API hostname param, which is CloudNameInternal
612       cloud_name_{{network.name_lower}}: {get_param: CloudNameInternal}
613 {%- elif network.name == 'StorageMgmt' %}
614   # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
615       cloud_name_{{network.name_lower}}: {get_param: CloudNameStorageManagement}
616 {%- else %}
617       cloud_name_{{network.name_lower}}: {get_param: CloudName{{network.name}}}
618 {%- endif %}
619 {%- endfor %}
620       cloud_name_ctlplane: {get_param: CloudNameCtlplane}
621       enabled_services:
622         list_join:
623           - ','
624 {% for role in roles %}
625           - {get_attr: [{{role.name}}ServiceNames, value]}
626 {% endfor %}
627       logging_groups:
628         yaql:
629           expression: >
630             $.data.groups.flatten()
631           data:
632             groups:
633 {% for role in roles %}
634               - {get_attr: [{{role.name}}ServiceChainRoleData, value, logging_groups]}
635 {% endfor %}
636       logging_sources:
637         yaql:
638           expression: >
639             $.data.sources.flatten()
640           data:
641             sources:
642 {% for role in roles %}
643               - {get_attr: [{{role.name}}ServiceChainRoleData, value, logging_sources]}
644 {% endfor %}
645       controller_ips: {get_attr: [{{primary_role_name}}, ip_address]}
646       controller_names: {get_attr: [{{primary_role_name}}, hostname]}
647       service_ips:
648         # Note (shardy) this somewhat complex yaql may be replaced
649         # with a map_deep_merge function in ocata.  It merges the
650         # list of maps, but appends to colliding lists when a service
651         # is deployed on more than one role
652         yaql:
653           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
654           data:
655             l:
656 {% for role in roles %}
657               - {get_attr: [{{role.name}}IpListMap, service_ips]}
658 {% endfor %}
659       service_node_names:
660         yaql:
661           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
662           data:
663             l:
664 {% for role in roles %}
665               - {get_attr: [{{role.name}}IpListMap, service_hostnames]}
666 {% endfor %}
667       short_service_node_names:
668         yaql:
669           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
670           data:
671             l:
672 {% for role in roles %}
673               - {get_attr: [{{role.name}}IpListMap, short_service_hostnames]}
674 {% endfor %}
675       short_service_bootstrap_node:
676         yaql:
677           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten().first()]))
678           data:
679             l:
680 {% for role in roles %}
681               - {get_attr: [{{role.name}}IpListMap, short_service_bootstrap_hostnames]}
682 {% endfor %}
683       NetVipMap: {get_attr: [VipMap, net_ip_map]}
684       RedisVirtualIP: {get_attr: [RedisVirtualIP, ip_address]}
685       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
686       DeployIdentifier: {get_param: DeployIdentifier}
687       UpdateIdentifier: {get_param: UpdateIdentifier}
688
689   MysqlRootPassword:
690     type: OS::TripleO::RandomString
691     properties:
692       length: 10
693
694   RabbitCookie:
695     type: OS::TripleO::RandomString
696     properties:
697       length: 20
698       salt: {get_param: RabbitCookieSalt}
699
700   DefaultPasswords:
701     type: OS::TripleO::DefaultPasswords
702     properties:
703       DefaultMysqlRootPassword: {get_attr: [MysqlRootPassword, value]}
704       DefaultRabbitCookie: {get_attr: [RabbitCookie, value]}
705       DefaultHeatAuthEncryptionKey: {get_attr: [HeatAuthEncryptionKey, value]}
706       DefaultPcsdPassword: {get_attr: [PcsdPassword, value]}
707       DefaultHorizonSecret: {get_attr: [HorizonSecret, value]}
708
709   # creates the network architecture
710   Networks:
711     type: OS::TripleO::Network
712
713   ControlVirtualIP:
714     type: OS::TripleO::Network::Ports::ControlPlaneVipPort
715     depends_on: Networks
716     properties:
717       name: control_virtual_ip
718       network: {get_param: NeutronControlPlaneID}
719       fixed_ips: {get_param: ControlFixedIPs}
720       replacement_policy: AUTO
721
722   RedisVirtualIP:
723     depends_on: Networks
724     type: OS::TripleO::Network::Ports::RedisVipPort
725     properties:
726       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
727       ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
728       PortName: redis_virtual_ip
729       NetworkName: {get_attr: [ServiceNetMap, service_net_map, RedisNetwork]}
730       ServiceName: redis
731       FixedIPs: {get_param: RedisVirtualFixedIPs}
732
733 {%- for network in networks if network.vip|default(false) %}
734 {%- if network.name == 'External' %}
735   # The public VIP is on the External net, falls back to ctlplane
736   PublicVirtualIP:
737     depends_on: Networks
738     type: OS::TripleO::Network::Ports::ExternalVipPort
739     properties:
740       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
741       ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
742       PortName: public_virtual_ip
743       FixedIPs: {get_param: PublicVirtualFixedIPs}
744 {%- elif network.name == 'StorageMgmt' %}
745   {{network.name}}VirtualIP:
746     depends_on: Networks
747     type: OS::TripleO::Network::Ports::{{network.name}}VipPort
748     properties:
749       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
750       PortName: storage_management_virtual_ip
751       FixedIPs: {get_param: {{network.name}}VirtualFixedIPs}
752 {%- else %}
753   {{network.name}}VirtualIP:
754     depends_on: Networks
755     type: OS::TripleO::Network::Ports::{{network.name}}VipPort
756     properties:
757       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
758       PortName: {{network.name_lower}}_virtual_ip
759       FixedIPs: {get_param: {{network.name}}VirtualFixedIPs}
760 {%- endif %}
761 {%- endfor %}
762
763   VipMap:
764     type: OS::TripleO::Network::Ports::NetVipMap
765     properties:
766       ControlPlaneIp: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
767 {%- for network in networks if network.vip|default(false) %}
768 {%- if network.name == 'External' %}
769       ExternalIp: {get_attr: [PublicVirtualIP, ip_address]}
770       ExternalIpUri: {get_attr: [PublicVirtualIP, ip_address_uri]}
771 {%- else %}
772       {{network.name}}Ip: {get_attr: [{{network.name}}VirtualIP, ip_address]}
773       {{network.name}}IpUri: {get_attr: [{{network.name}}VirtualIP, ip_address_uri]}
774 {%- endif %}
775 {%- endfor %}
776       # No tenant or management VIP required
777     # Because of nested get_attr functions in the KeystoneAdminVip output, we
778     # can't determine which attributes of VipMap are used until after
779     # ServiceNetMap's attribute values are available.
780     depends_on: ServiceNetMap
781
782   # All Nodes Validations
783   AllNodesValidationConfig:
784     type: OS::TripleO::AllNodes::Validation
785     properties:
786       PingTestIps:
787         list_join:
788         - ' '
789         -
790 {%- for network in networks if network.enabled|default(true) %}
791           - yaql:
792               expression: coalesce($.data, []).first(null)
793               data: {get_attr: [{{primary_role_name}}, {{network.name_lower}}_ip_address]}
794 {%- endfor %}
795
796   UpdateWorkflow:
797     type: OS::TripleO::Tasks::UpdateWorkflow
798     depends_on:
799 {% for role in roles %}
800       - {{role.name}}AllNodesDeployment
801 {% endfor %}
802     properties:
803       servers:
804 {% for role in roles %}
805         {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
806 {% endfor %}
807       input_values:
808         deploy_identifier: {get_param: DeployIdentifier}
809         update_identifier: {get_param: UpdateIdentifier}
810
811   # Optional ExtraConfig for all nodes - all roles are passed in here, but
812   # the nested template may configure each role differently (or not at all)
813   AllNodesExtraConfig:
814     type: OS::TripleO::AllNodesExtraConfig
815     depends_on:
816       - UpdateWorkflow
817 {% for role in roles %}
818       - {{role.name}}AllNodesValidationDeployment
819 {% endfor %}
820     properties:
821       servers:
822 {% for role in roles %}
823         {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
824 {% endfor %}
825
826   # Post deployment steps for all roles
827   AllNodesDeploySteps:
828     type: OS::TripleO::PostDeploySteps
829     depends_on:
830       - AllNodesExtraConfig
831 {% for role in roles %}
832       - {{role.name}}AllNodesDeployment
833 {% endfor %}
834     properties:
835       servers:
836 {% for role in roles %}
837         {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
838 {% endfor %}
839       stack_name: {get_param: 'OS::stack_name'}
840       EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
841       ctlplane_service_ips:
842         # Note (shardy) this somewhat complex yaql may be replaced
843         # with a map_deep_merge function in ocata.  It merges the
844         # list of maps, but appends to colliding lists when a service
845         # is deployed on more than one role
846         yaql:
847           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
848           data:
849             l:
850 {% for role in roles %}
851               - {get_attr: [{{role.name}}IpListMap, ctlplane_service_ips]}
852 {% endfor %}
853       role_data:
854 {% for role in roles %}
855         {{role.name}}:
856           map_merge:
857           - {get_attr: [{{role.name}}ServiceChainRoleData, value]}
858           - {get_attr: [{{role.name}}MergedConfigSettings, value]}
859 {% endfor %}
860
861   ServerOsCollectConfigData:
862     type: OS::Heat::Value
863     properties:
864       type: json
865       value:
866 {% for role in roles %}
867         {{role.name}}: {get_attr: [{{role.name}}, attributes, os_collect_config]}
868 {% endfor %}
869
870   DeployedServerEnvironment:
871     type: OS::TripleO::DeployedServerEnvironment
872     properties:
873       RoleCounts:
874 {% for role in roles %}
875         {{role.name}}DeployedServerCount: {get_param: {{role.name}}Count}
876 {% endfor %}
877       VipMap:
878         map_merge:
879           - {get_attr: [VipMap, net_ip_map]}
880           - redis: {get_attr: [RedisVirtualIP, ip_address]}
881       DeployedServerPortMap:
882         map_merge:
883           list_concat:
884 {% for role in roles %}
885               - {get_attr: [{{role.name}}, deployed_server_port_map]}
886 {% endfor %}
887       DeployedServerDeploymentSwiftDataMap:
888         map_merge:
889           list_concat:
890 {% for role in roles %}
891               - {get_attr: [{{role.name}}, deployed_server_deployment_swift_data_map]}
892 {% endfor %}
893       DefaultRouteIp:
894         str_split:
895           - ':'
896           - str_split:
897             - '/'
898             - {get_attr: [ServerOsCollectConfigData, value, {{primary_role_name}}, '0', request, metadata_url]}
899             - 2
900           - 0
901
902 outputs:
903   ManagedEndpoints:
904     description: Asserts that the keystone endpoints have been provisioned.
905     value: true
906   KeystoneURL:
907     description: URL for the Overcloud Keystone service
908     value: {get_attr: [EndpointMapData, value, KeystonePublic, uri]}
909   KeystoneAdminVip:
910     description: Keystone Admin VIP endpoint
911     # Note that these nested get_attr functions require a dependency
912     # relationship between VipMap and ServiceNetMap, since we can't determine
913     # which attributes of VipMap are used until after ServiceNetMap's attribute
914     # values are available. If this is ever reworked to not use nested
915     # get_attr, that dependency can be removed.
916     value: {get_attr: [VipMap, net_ip_map, {get_attr: [ServiceNetMap, service_net_map, KeystoneAdminApiNetwork]}]}
917   EndpointMap:
918     description: |
919       Mapping of the resources with the needed info for their endpoints.
920       This includes the protocol used, the IP, port and also a full
921       representation of the URI.
922     value: {get_attr: [EndpointMapData, value]}
923   HostsEntry:
924     description: |
925       The content that should be appended to your /etc/hosts if you want to get
926       hostname-based access to the deployed nodes (useful for testing without
927       setting up a DNS).
928     value:
929       list_join:
930       - "\n"
931       - - {get_attr: [hostsConfig, hosts_entries]}
932       - - {get_attr: [VipHosts, value]}
933   EnabledServices:
934     description: The services enabled on each role
935     value:
936 {% for role in roles %}
937       {{role.name}}: {get_attr: [{{role.name}}ServiceNames, value]}
938 {% endfor %}
939   RoleData:
940     description: The configuration data associated with each role
941     value:
942 {% for role in roles %}
943       {{role.name}}:
944         map_merge:
945         - {get_attr: [{{role.name}}ServiceChainRoleData, value]}
946         - {get_attr: [{{role.name}}MergedConfigSettings, value]}
947 {% endfor %}
948   RoleConfig:
949     description: The configuration workflows associated with each role
950     value: {get_attr: [AllNodesDeploySteps, RoleConfig]}
951   RoleNetIpMap:
952     description: Mapping of each network to a list of IPs for each role
953     value:
954 {% for role in roles %}
955       {{role.name}}: {get_attr: [{{role.name}}IpListMap, net_ip_map]}
956 {% endfor %}
957   RoleNetHostnameMap:
958     description: Mapping of each network to a list of hostnames for each role
959     value:
960 {% for role in roles %}
961       {{role.name}}: {get_attr: [{{role.name}}NetworkHostnameMap, value]}
962 {% endfor %}
963   ServerOsCollectConfigData:
964     description: The os-collect-config configuration associated with each server resource
965     value: {get_attr: [ServerOsCollectConfigData, value]}
966   VipMap:
967     description: Mapping of each network to VIP addresses. Also includes the Redis VIP.
968     value:
969       map_merge:
970         - {get_attr: [VipMap, net_ip_map]}
971         - redis: {get_attr: [RedisVirtualIP, ip_address]}
972   ServerIdData:
973     description: Mapping of each role to a list of nova server IDs and the bootstrap ID
974     value: {get_attr: [ServerIdMap, value]}
975   DeployedServerEnvironment:
976     description:
977       Environment data that can be used as input into the services stack when
978       using split-stack.
979     value: {get_attr: [DeployedServerEnvironment, deployed_server_environment]}