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