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