Merge "Add RoleNetIpMap output to overcloud.j2.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: ocata
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: Should be used for arbitrary ips.
55     type: json
56   InternalApiVirtualFixedIPs:
57     default: []
58     description: >
59         Control the IP allocation for the InternalApiVirtualInterface port. E.g.
60         [{'ip_address':'1.2.3.4'}]
61     type: json
62   NeutronControlPlaneID:
63     default: 'ctlplane'
64     type: string
65     description: Neutron ID or name for ctlplane network.
66   NeutronPublicInterface:
67     default: nic1
68     description: What interface to bridge onto br-ex for network nodes.
69     type: string
70   PublicVirtualFixedIPs:
71     default: []
72     description: >
73         Control the IP allocation for the PublicVirtualInterface port. E.g.
74         [{'ip_address':'1.2.3.4'}]
75     type: json
76   RabbitCookieSalt:
77     type: string
78     default: unset
79     description: Salt for the rabbit cookie, change this to force the randomly generated rabbit cookie to change.
80   StorageVirtualFixedIPs:
81     default: []
82     description: >
83         Control the IP allocation for the StorageVirtualInterface port. E.g.
84         [{'ip_address':'1.2.3.4'}]
85     type: json
86   StorageMgmtVirtualFixedIPs:
87     default: []
88     description: >
89         Control the IP allocation for the StorageMgmgVirtualInterface port. E.g.
90         [{'ip_address':'1.2.3.4'}]
91     type: json
92   RedisVirtualFixedIPs:
93     default: []
94     description: >
95         Control the IP allocation for the virtual IP used by Redis. E.g.
96         [{'ip_address':'1.2.3.4'}]
97     type: json
98   CloudDomain:
99     default: 'localdomain'
100     type: string
101     description: >
102       The DNS domain used for the hosts. This should match the dhcp_domain
103       configured in the Undercloud neutron. Defaults to localdomain.
104   ServerMetadata:
105     default: {}
106     description: >
107       Extra properties or metadata passed to Nova for the created nodes in
108       the overcloud. It's accessible via the Nova metadata API.
109     type: json
110
111 # Compute-specific params
112 # FIXME(shardy) handle these deprecated names as they don't match compute.yaml
113   HypervisorNeutronPhysicalBridge:
114     default: 'br-ex'
115     description: >
116       An OVS bridge to create on each hypervisor. This defaults to br-ex the
117       same as the control plane nodes, as we have a uniform configuration of
118       the openvswitch agent. Typically should not need to be changed.
119     type: string
120   HypervisorNeutronPublicInterface:
121     default: nic1
122     description: What interface to add to the HypervisorNeutronPhysicalBridge.
123     type: string
124
125   NodeCreateBatchSize:
126     default: 30
127     description: Maxiumum batch size for creating nodes
128     type: number
129
130   # Jinja loop for Role in role_data.yaml
131 {% for role in roles %}
132   # Parameters generated for {{role.name}} Role
133   {{role.name}}Services:
134     description: A list of service resources (configured in the Heat
135                  resource_registry) which represent nested stacks
136                  for each service that should get installed on the {{role.name}} role.
137     type: comma_delimited_list
138
139   {{role.name}}Count:
140     description: Number of {{role.name}} nodes to deploy
141     type: number
142     default: {{role.CountDefault|default(0)}}
143
144   {{role.name}}HostnameFormat:
145     type: string
146     description: >
147       Format for {{role.name}} node hostnames
148       Note %index% is translated into the index of the node, e.g 0/1/2 etc
149       and %stackname% is replaced with the stack name e.g overcloud
150   {% if role.HostnameFormatDefault %}
151     default: "{{role.HostnameFormatDefault}}"
152   {% else %}
153     default: "%stackname%-{{role.name.lower()}}-%index%"
154   {% endif %}
155
156   {{role.name}}RemovalPolicies:
157     default: []
158     type: json
159     description: >
160       List of resources to be removed from {{role.name}} ResourceGroup when
161       doing an update which requires removal of specific resources.
162       Example format ComputeRemovalPolicies: [{'resource_list': ['0']}]
163
164 {% if role.name != 'Compute' %}
165   {{role.name}}SchedulerHints:
166 {% else %}
167   NovaComputeSchedulerHints:
168 {% endif %}
169     type: json
170     description: Optional scheduler hints to pass to nova
171     default: {}
172 {% endfor %}
173
174   # Identifiers to trigger tasks on nodes
175   UpdateIdentifier:
176     default: ''
177     type: string
178     description: >
179       Setting to a previously unused value during stack-update will trigger
180       package update on all nodes
181   DeployIdentifier:
182     default: ''
183     type: string
184     description: >
185       Setting this to a unique value will re-run any deployment tasks which
186       perform configuration on a Heat stack-update.
187   AddVipsToEtcHosts:
188     default: True
189     type: boolean
190     description: >
191       Set to true to append per network Vips to /etc/hosts on each node.
192
193 conditions:
194   add_vips_to_etc_hosts: {equals : [{get_param: AddVipsToEtcHosts}, True]}
195
196 resources:
197
198   VipHosts:
199     type: OS::Heat::Value
200     properties:
201       type: string
202       value:
203         list_join:
204         - "\n"
205         - - str_replace:
206               template: IP  HOST
207               params:
208                 IP: {get_attr: [VipMap, net_ip_map, external]}
209                 HOST: {get_param: CloudName}
210           - str_replace:
211               template: IP  HOST
212               params:
213                 IP: {get_attr: [VipMap, net_ip_map, ctlplane]}
214                 HOST: {get_param: CloudNameCtlplane}
215           - str_replace:
216               template: IP  HOST
217               params:
218                 IP: {get_attr: [VipMap, net_ip_map, internal_api]}
219                 HOST: {get_param: CloudNameInternal}
220           - str_replace:
221               template: IP  HOST
222               params:
223                 IP: {get_attr: [VipMap, net_ip_map, storage]}
224                 HOST: {get_param: CloudNameStorage}
225           - str_replace:
226               template: IP  HOST
227               params:
228                 IP: {get_attr: [VipMap, net_ip_map, storage_mgmt]}
229                 HOST: {get_param: CloudNameStorageManagement}
230
231   HeatAuthEncryptionKey:
232     type: OS::Heat::RandomString
233
234   PcsdPassword:
235     type: OS::Heat::RandomString
236     properties:
237       length: 16
238
239   HorizonSecret:
240     type: OS::Heat::RandomString
241     properties:
242       length: 10
243
244   ServiceNetMap:
245     type: OS::TripleO::ServiceNetMap
246
247   EndpointMap:
248     type: OS::TripleO::EndpointMap
249     properties:
250       CloudEndpoints:
251         external: {get_param: CloudName}
252         internal_api: {get_param: CloudNameInternal}
253         storage: {get_param: CloudNameStorage}
254         storage_mgmt: {get_param: CloudNameStorageManagement}
255         ctlplane: {get_param: CloudNameCtlplane}
256       NetIpMap: {get_attr: [VipMap, net_ip_map]}
257       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
258
259   EndpointMapData:
260     type: OS::Heat::Value
261     properties:
262       type: json
263       value: {get_attr: [EndpointMap, endpoint_map]}
264
265   # Jinja loop for Role in roles_data.yaml
266 {% for role in roles %}
267   # Resources generated for {{role.name}} Role
268   {{role.name}}ServiceChain:
269     type: OS::TripleO::Services
270     properties:
271       Services:
272         get_param: {{role.name}}Services
273       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
274       EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
275       DefaultPasswords: {get_attr: [DefaultPasswords, passwords]}
276
277   # Filter any null/None service_names which may be present due to mapping
278   # of services to OS::Heat::None
279   {{role.name}}ServiceNames:
280     type: OS::Heat::Value
281     depends_on: {{role.name}}ServiceChain
282     properties:
283       type: comma_delimited_list
284       value:
285         yaql:
286           expression: coalesce($.data, []).where($ != null)
287           data: {get_attr: [{{role.name}}ServiceChain, role_data, service_names]}
288
289   {{role.name}}HostsDeployment:
290     type: OS::Heat::StructuredDeployments
291     properties:
292       name: {{role.name}}HostsDeployment
293       config: {get_attr: [hostsConfig, config_id]}
294       servers: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
295
296   {{role.name}}AllNodesDeployment:
297     type: OS::Heat::StructuredDeployments
298     depends_on:
299 {% for role_inner in roles %}
300       - {{role_inner.name}}HostsDeployment
301 {% endfor %}
302     properties:
303       name: {{role.name}}AllNodesDeployment
304       config: {get_attr: [allNodesConfig, config_id]}
305       servers: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
306       input_values:
307         # Note we have to use yaql to look up the first hostname/ip in the
308         # list because heat path based attributes operate on the attribute
309         # inside the ResourceGroup, not the exposed list ref discussion in
310         # https://bugs.launchpad.net/heat/+bug/1640488
311         # The coalesce is needed because $.data is None during heat validation
312         bootstrap_nodeid:
313           yaql:
314             expression: coalesce($.data, []).first(null)
315             data: {get_attr: [{{role.name}}, hostname]}
316         bootstrap_nodeid_ip:
317           yaql:
318             expression: coalesce($.data, []).first(null)
319             data: {get_attr: [{{role.name}}, ip_address]}
320
321   {{role.name}}AllNodesValidationDeployment:
322     type: OS::Heat::StructuredDeployments
323     depends_on: {{role.name}}AllNodesDeployment
324     properties:
325       name: {{role.name}}AllNodesValidationDeployment
326       config: {get_resource: AllNodesValidationConfig}
327       servers: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
328
329   {{role.name}}IpListMap:
330     type: OS::TripleO::Network::Ports::NetIpListMap
331     properties:
332       ControlPlaneIpList: {get_attr: [{{role.name}}, ip_address]}
333       ExternalIpList: {get_attr: [{{role.name}}, external_ip_address]}
334       InternalApiIpList: {get_attr: [{{role.name}}, internal_api_ip_address]}
335       StorageIpList: {get_attr: [{{role.name}}, storage_ip_address]}
336       StorageMgmtIpList: {get_attr: [{{role.name}}, storage_mgmt_ip_address]}
337       TenantIpList: {get_attr: [{{role.name}}, tenant_ip_address]}
338       ManagementIpList: {get_attr: [{{role.name}}, management_ip_address]}
339       EnabledServices: {get_attr: [{{role.name}}ServiceNames, value]}
340       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
341       ServiceHostnameList: {get_attr: [{{role.name}}, hostname]}
342       NetworkHostnameMap:
343         # Note (shardy) this somewhat complex yaql may be replaced
344         # with a map_deep_merge function in ocata.  It merges the
345         # list of maps, but appends to colliding lists so we can
346         # create a map of lists for all nodes for each network
347         yaql:
348           expression: dict($.data.where($ != null).flatten().selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
349           data:
350             - {get_attr: [{{role.name}}, hostname_map]}
351
352   {{role.name}}:
353     type: OS::Heat::ResourceGroup
354     depends_on: Networks
355     update_policy:
356       batch_create:
357         max_batch_size: {get_param: NodeCreateBatchSize}
358     properties:
359       count: {get_param: {{role.name}}Count}
360       removal_policies: {get_param: {{role.name}}RemovalPolicies}
361       resource_def:
362         type: OS::TripleO::{{role.name}}
363         properties:
364           CloudDomain: {get_param: CloudDomain}
365           ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}
366           EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
367           Hostname:
368             str_replace:
369               template: {get_param: {{role.name}}HostnameFormat}
370               params:
371                 '%stackname%': {get_param: 'OS::stack_name'}
372           NodeIndex: '%index%'
373   {% if role.name != 'Compute' %}
374           {{role.name}}SchedulerHints: {get_param: {{role.name}}SchedulerHints}
375   {% else %}
376           NovaComputeSchedulerHints: {get_param: NovaComputeSchedulerHints}
377   {% endif %}
378           ServiceConfigSettings:
379             map_merge:
380               -  get_attr: [{{role.name}}ServiceChain, role_data, config_settings]
381           {% for r in roles %}
382               - get_attr: [{{r.name}}ServiceChain, role_data, global_config_settings]
383           {% endfor %}
384               # This next step combines two yaql passes:
385               # - The inner one does a deep merge on the service_config_settings for all roles
386               # - The outer one filters the map based on the services enabled for the role
387               #   then merges the result into one map.
388               - yaql:
389                   expression: let(root => $) -> $.data.map.items().where($[0] in coalesce($root.data.services, [])).select($[1]).reduce($1.mergeWith($2), {})
390                   data:
391                     map:
392                       yaql:
393                         expression: $.data.where($ != null).reduce($1.mergeWith($2), {})
394                         data:
395                         {% for r in roles %}
396                           - get_attr: [{{r.name}}ServiceChain, role_data, service_config_settings]
397                         {% endfor %}
398                     services: {get_attr: [{{role.name}}ServiceNames, value]}
399           ServiceNames: {get_attr: [{{role.name}}ServiceNames, value]}
400           MonitoringSubscriptions: {get_attr: [{{role.name}}ServiceChain, role_data, monitoring_subscriptions]}
401           ServiceMetadataSettings: {get_attr: [{{role.name}}ServiceChain, role_data, service_metadata_settings]}
402 {% endfor %}
403
404   hostsConfig:
405     type: OS::TripleO::Hosts::SoftwareConfig
406     properties:
407       hosts:
408         list_join:
409         - "\n"
410         - - if:
411             - add_vips_to_etc_hosts
412             - {get_attr: [VipHosts, value]}
413             - ''
414         -
415 {% for role in roles %}
416           - list_join:
417             - ""
418             - {get_attr: [{{role.name}}, hosts_entry]}
419 {% endfor %}
420
421   allNodesConfig:
422     type: OS::TripleO::AllNodes::SoftwareConfig
423     properties:
424       cloud_name_external: {get_param: CloudName}
425       cloud_name_internal_api: {get_param: CloudNameInternal}
426       cloud_name_storage: {get_param: CloudNameStorage}
427       cloud_name_storage_mgmt: {get_param: CloudNameStorageManagement}
428       cloud_name_ctlplane: {get_param: CloudNameCtlplane}
429       enabled_services:
430         list_join:
431           - ','
432 {% for role in roles %}
433           - {get_attr: [{{role.name}}ServiceNames, value]}
434 {% endfor %}
435       logging_groups:
436         yaql:
437           expression: >
438             $.data.groups.flatten()
439           data:
440             groups:
441 {% for role in roles %}
442               - {get_attr: [{{role.name}}ServiceChain, role_data, logging_groups]}
443 {% endfor %}
444       logging_sources:
445         yaql:
446           expression: >
447             $.data.sources.flatten()
448           data:
449             sources:
450 {% for role in roles %}
451               - {get_attr: [{{role.name}}ServiceChain, role_data, logging_sources]}
452 {% endfor %}
453       controller_ips: {get_attr: [{{primary_role_name}}, ip_address]}
454       controller_names: {get_attr: [{{primary_role_name}}, hostname]}
455       service_ips:
456         # Note (shardy) this somewhat complex yaql may be replaced
457         # with a map_deep_merge function in ocata.  It merges the
458         # list of maps, but appends to colliding lists when a service
459         # is deployed on more than one role
460         yaql:
461           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
462           data:
463             l:
464 {% for role in roles %}
465               - {get_attr: [{{role.name}}IpListMap, service_ips]}
466 {% endfor %}
467       service_node_names:
468         yaql:
469           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
470           data:
471             l:
472 {% for role in roles %}
473               - {get_attr: [{{role.name}}IpListMap, service_hostnames]}
474 {% endfor %}
475       short_service_node_names:
476         yaql:
477           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten()]))
478           data:
479             l:
480 {% for role in roles %}
481               - {get_attr: [{{role.name}}IpListMap, short_service_hostnames]}
482 {% endfor %}
483       short_service_bootstrap_node:
484         yaql:
485           expression: dict($.data.l.where($ != null).selectMany($.items()).groupBy($[0], $[1], [$[0], $[1].flatten().first()]))
486           data:
487             l:
488 {% for role in roles %}
489               - {get_attr: [{{role.name}}IpListMap, short_service_bootstrap_hostnames]}
490 {% endfor %}
491       # FIXME(shardy): These require further work to move into service_ips
492       memcache_node_ips: {get_attr: [{{primary_role_name}}IpListMap, net_ip_map, {get_attr: [ServiceNetMap, service_net_map, MemcachedNetwork]}]}
493       NetVipMap: {get_attr: [VipMap, net_ip_map]}
494       RedisVirtualIP: {get_attr: [RedisVirtualIP, ip_address]}
495       ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
496       DeployIdentifier: {get_param: DeployIdentifier}
497       UpdateIdentifier: {get_param: UpdateIdentifier}
498
499   MysqlRootPassword:
500     type: OS::Heat::RandomString
501     properties:
502       length: 10
503
504   RabbitCookie:
505     type: OS::Heat::RandomString
506     properties:
507       length: 20
508       salt: {get_param: RabbitCookieSalt}
509
510   DefaultPasswords:
511     type: OS::TripleO::DefaultPasswords
512     properties:
513       DefaultMysqlRootPassword: {get_attr: [MysqlRootPassword, value]}
514       DefaultRabbitCookie: {get_attr: [RabbitCookie, value]}
515       DefaultHeatAuthEncryptionKey: {get_attr: [HeatAuthEncryptionKey, value]}
516       DefaultPcsdPassword: {get_attr: [PcsdPassword, value]}
517       DefaultHorizonSecret: {get_attr: [HorizonSecret, value]}
518
519   # creates the network architecture
520   Networks:
521     type: OS::TripleO::Network
522
523   ControlVirtualIP:
524     type: OS::TripleO::Network::Ports::ControlPlaneVipPort
525     depends_on: Networks
526     properties:
527       name: control_virtual_ip
528       network: {get_param: NeutronControlPlaneID}
529       fixed_ips: {get_param: ControlFixedIPs}
530       replacement_policy: AUTO
531
532   RedisVirtualIP:
533     depends_on: Networks
534     type: OS::TripleO::Network::Ports::RedisVipPort
535     properties:
536       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
537       ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
538       PortName: redis_virtual_ip
539       NetworkName: {get_attr: [ServiceNetMap, service_net_map, RedisNetwork]}
540       ServiceName: redis
541       FixedIPs: {get_param: RedisVirtualFixedIPs}
542
543   # The public VIP is on the External net, falls back to ctlplane
544   PublicVirtualIP:
545     depends_on: Networks
546     type: OS::TripleO::Network::Ports::ExternalVipPort
547     properties:
548       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
549       ControlPlaneNetwork: {get_param: NeutronControlPlaneID}
550       PortName: public_virtual_ip
551       FixedIPs: {get_param: PublicVirtualFixedIPs}
552
553   InternalApiVirtualIP:
554     depends_on: Networks
555     type: OS::TripleO::Network::Ports::InternalApiVipPort
556     properties:
557       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
558       PortName: internal_api_virtual_ip
559       FixedIPs: {get_param: InternalApiVirtualFixedIPs}
560
561   StorageVirtualIP:
562     depends_on: Networks
563     type: OS::TripleO::Network::Ports::StorageVipPort
564     properties:
565       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
566       PortName: storage_virtual_ip
567       FixedIPs: {get_param: StorageVirtualFixedIPs}
568
569   StorageMgmtVirtualIP:
570     depends_on: Networks
571     type: OS::TripleO::Network::Ports::StorageMgmtVipPort
572     properties:
573       ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
574       PortName: storage_management_virtual_ip
575       FixedIPs: {get_param: StorageMgmtVirtualFixedIPs}
576
577   VipMap:
578     type: OS::TripleO::Network::Ports::NetVipMap
579     properties:
580       ControlPlaneIp: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
581       ExternalIp: {get_attr: [PublicVirtualIP, ip_address]}
582       ExternalIpUri: {get_attr: [PublicVirtualIP, ip_address_uri]}
583       InternalApiIp: {get_attr: [InternalApiVirtualIP, ip_address]}
584       InternalApiIpUri: {get_attr: [InternalApiVirtualIP, ip_address_uri]}
585       StorageIp: {get_attr: [StorageVirtualIP, ip_address]}
586       StorageIpUri: {get_attr: [StorageVirtualIP, ip_address_uri]}
587       StorageMgmtIp: {get_attr: [StorageMgmtVirtualIP, ip_address]}
588       StorageMgmtIpUri: {get_attr: [StorageMgmtVirtualIP, ip_address_uri]}
589       # No tenant or management VIP required
590
591   # All Nodes Validations
592   AllNodesValidationConfig:
593     type: OS::TripleO::AllNodes::Validation
594     properties:
595       PingTestIps:
596         list_join:
597         - ' '
598         - - yaql:
599               expression: coalesce($.data, []).first(null)
600               data: {get_attr: [{{primary_role_name}}, external_ip_address]}
601           - yaql:
602               expression: coalesce($.data, []).first(null)
603               data: {get_attr: [{{primary_role_name}}, internal_api_ip_address]}
604           - yaql:
605               expression: coalesce($.data, []).first(null)
606               data: {get_attr: [{{primary_role_name}}, storage_ip_address]}
607           - yaql:
608               expression: coalesce($.data, []).first(null)
609               data: {get_attr: [{{primary_role_name}}, storage_mgmt_ip_address]}
610           - yaql:
611               expression: coalesce($.data, []).first(null)
612               data: {get_attr: [{{primary_role_name}}, tenant_ip_address]}
613           - yaql:
614               expression: coalesce($.data, []).first(null)
615               data: {get_attr: [{{primary_role_name}}, management_ip_address]}
616
617   UpdateWorkflow:
618     type: OS::TripleO::Tasks::UpdateWorkflow
619     depends_on:
620 {% for role in roles %}
621       - {{role.name}}AllNodesDeployment
622 {% endfor %}
623     properties:
624       servers:
625 {% for role in roles %}
626         {{role.name}}: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
627 {% endfor %}
628       input_values:
629         deploy_identifier: {get_param: DeployIdentifier}
630         update_identifier: {get_param: UpdateIdentifier}
631
632   # Optional ExtraConfig for all nodes - all roles are passed in here, but
633   # the nested template may configure each role differently (or not at all)
634   AllNodesExtraConfig:
635     type: OS::TripleO::AllNodesExtraConfig
636     depends_on:
637       - UpdateWorkflow
638 {% for role in roles %}
639       - {{role.name}}AllNodesValidationDeployment
640 {% endfor %}
641     properties:
642       servers:
643 {% for role in roles %}
644         {{role.name}}: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
645 {% endfor %}
646
647   # Post deployment steps for all roles
648   AllNodesDeploySteps:
649     type: OS::TripleO::PostDeploySteps
650     depends_on:
651 {% for role in roles %}
652       - {{role.name}}AllNodesDeployment
653 {% endfor %}
654     properties:
655       servers:
656 {% for role in roles %}
657         {{role.name}}: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
658 {% endfor %}
659       EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
660       role_data:
661 {% for role in roles %}
662         {{role.name}}: {get_attr: [{{role.name}}ServiceChain, role_data]}
663 {% endfor %}
664
665 outputs:
666   ManagedEndpoints:
667     description: Asserts that the keystone endpoints have been provisioned.
668     value: true
669   KeystoneURL:
670     description: URL for the Overcloud Keystone service
671     value: {get_attr: [EndpointMapData, value, KeystonePublic, uri]}
672   KeystoneAdminVip:
673     description: Keystone Admin VIP endpoint
674     value: {get_attr: [VipMap, net_ip_map, {get_attr: [ServiceNetMap, service_net_map, KeystoneAdminApiNetwork]}]}
675   EndpointMap:
676     description: |
677       Mapping of the resources with the needed info for their endpoints.
678       This includes the protocol used, the IP, port and also a full
679       representation of the URI.
680     value: {get_attr: [EndpointMapData, value]}
681   HostsEntry:
682     description: |
683       The content that should be appended to your /etc/hosts if you want to get
684       hostname-based access to the deployed nodes (useful for testing without
685       setting up a DNS).
686     value:
687       list_join:
688       - "\n"
689       - - {get_attr: [hostsConfig, hosts_entries]}
690       - - {get_attr: [VipHosts, value]}
691   EnabledServices:
692     description: The services enabled on each role
693     value:
694 {% for role in roles %}
695       {{role.name}}: {get_attr: [{{role.name}}ServiceNames, value]}
696 {% endfor %}
697   RoleData:
698     description: The configuration data associated with each role
699     value:
700 {% for role in roles %}
701       {{role.name}}: {get_attr: [{{role.name}}ServiceChain, role_data]}
702 {% endfor %}
703   RoleNetIpMap:
704     description: Mapping of each network to a list of IPs for each role
705     value:
706 {% for role in roles %}
707       {{role.name}}: {get_attr: [{{role.name}}IpListMap, net_ip_map]}
708 {% endfor %}