Add heat template to deploy onap infrastructure

This change adds a heat template that
deploys empty onap infrastructure on open stack.
Infrastructure consists of an installer instance + infra instance +
specified number of kubernetes nodes.
All instances are empty after creation, and live in the internal network with
cidr 10.1.0.0/24.
They are isolated by security groups in order not to have
external network access except possibly the intranet, but
it is possible to enable internet access if required.

Change-Id: I70024e1e2344ed75f443f03b2239b460a71d0151
Issue-ID: OOM-2042
Signed-off-by: Michal Zegan <m.zegan@samsung.com>
diff --git a/tools/cicdansible/heat/installer.yaml b/tools/cicdansible/heat/installer.yaml
new file mode 100644
index 0000000..8fff3a7
--- /dev/null
+++ b/tools/cicdansible/heat/installer.yaml
@@ -0,0 +1,283 @@
+#This is the environment heat template, compatible with openstack ocata.
+heat_template_version: 2017-02-24
+description: "Heat template for deploying onap env"
+parameters:
+  auth_key:
+    label: "Auth public key"
+    description: "The public key used to authenticate to instances"
+    type: string
+  node_flavor_name:
+    label: "name of node flavor"
+    description: "The name of the flavor used to create kubernetes nodes"
+    type: string
+    constraints:
+      - custom_constraint: nova.flavor
+        description: "need to specify a valid flavor"
+  infra_flavor_name:
+    label: "name of infra flavor"
+    description: "flavor used to create infra instance"
+    type: string
+    constraints:
+      - custom_constraint: nova.flavor
+        description: "need to specify a valid flavor"
+  installer_flavor_name:
+    label: "name of installer flavor"
+    description: "flavor used to create installer instance"
+    type: string
+    constraints:
+      - custom_constraint: nova.flavor
+        description: "need to specify a valid flavor"
+  image_name:
+    label: "image name"
+    description: "name of the image from which to create all instances, should be rhel 7.6 or centos image"
+    type: string
+    constraints:
+      - custom_constraint: glance.image
+        description: "must specify a valid image name"
+  subnet_cidr:
+    label: "private subnet cidr"
+    description: "Cidr of a private subnet instances will be connected to"
+    type: string
+    constraints:
+      - custom_constraint: net_cidr
+  subnet_range_start:
+    label: "subnet dhcp allocation range start"
+    description: "Start of range of dhcp allocatable ips on private subnet"
+    type: string
+    constraints:
+      - custom_constraint: ip_addr
+  subnet_range_end:
+    label: "end of subnet dhcp allocation range"
+    description: "End of private subnet's dhcp allocation range"
+    type: string
+    constraints:
+      - custom_constraint: ip_addr
+  router_addr:
+    label: "ip address of router"
+    description: "IP address of the router allowing access to other networks incl. company network"
+    type: string
+    constraints:
+      - custom_constraint: ip_addr
+  public_network_name:
+    label: "name of the public network"
+    description: "Name of the public, internet facing network, also allowing access to company internal hosts"
+    type: string
+    constraints:
+      - custom_constraint: neutron.network
+        description: "Must specify a valid network name or id"
+  external_subnet_cidr:
+    label: "external subnet cidr"
+    description: "The CIDR of the external subnet, that should be accessible from instances, even when internet access is cut. Putting 0.0.0.0/0 here means access to internet."
+    type: string
+    constraints:
+      - custom_constraint: net_cidr
+  installer_ip:
+    label: "floating ip of the installer"
+    description: "a pre-allocated floating ip that will be associated with the installer instance"
+    type: string
+  infra_ip:
+    label: "floating ip of the infra"
+    description: "a pre-allocated floating ip that will be associated with the infrastructure instance"
+    type: string
+  node_ip:
+    label: "floating ip of the first node"
+    description: "a pre-allocated floating ip that will be associated with the first kubernetes node and allow accessing onap"
+    type: string
+  num_nodes:
+    label: "num nodes"
+    description: "the number of kubernetes nodes to create, min 1"
+    type: number
+    constraints:
+      - range: { min: 1 }
+        description: "must be a positive number"
+resources:
+  # Security group used to secure access to instances.
+  secgroup:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      rules:
+        # Egress rule allowing access to external_subnet_cidr.
+        - direction: egress
+          ethertype: IPv4
+          remote_ip_prefix: { get_param: external_subnet_cidr }
+        # Ingress rule, allowing also inbound access by external network.
+        - direction: ingress
+          ethertype: IPv4
+          remote_ip_prefix: { get_param: external_subnet_cidr }
+        # Allow outbound communication with the internal subnet.
+        - direction: egress
+          ethertype: IPv4
+          remote_ip_prefix: { get_param: subnet_cidr }
+        # Allow inbound communication from internal network.
+        - direction: ingress
+          ethertype: IPv4
+          remote_ip_prefix: { get_param: subnet_cidr }
+        # Allow outbound access to 169.254.0.0/16, mainly for metadata. We do not need inbound.
+        - direction: egress
+          ethertype: IPv4
+          remote_ip_prefix: 169.254.0.0/16
+  #A network that our test environment will be connected to.
+  privnet:
+    type: OS::Neutron::Net
+  #Subnet that instances will live in.
+  privsubnet:
+    type: OS::Neutron::Subnet
+    properties:
+      network: { get_resource: privnet }
+      cidr: { get_param: subnet_cidr }
+      allocation_pools:
+        - { start: { get_param: subnet_range_start }, end: { get_param: subnet_range_end } }
+      gateway_ip: { get_param: router_addr }
+      ip_version: 4
+  #A port connected to the private network, taken by router.
+  routerport:
+    type: OS::Neutron::Port
+    properties:
+      network: { get_resource: privnet }
+      fixed_ips:
+        - { subnet: { get_resource: privsubnet }, ip_address: { get_param: router_addr } }
+      security_groups: [{ get_resource: secgroup }]
+  #This is a router, routing between us and the internet.
+  #It has an external gateway to public network.
+  router:
+    type: OS::Neutron::Router
+    properties:
+      external_gateway_info:
+        network: { get_param: public_network_name }
+  #This is a router interface connecting it to our private subnet's router port.
+  routercon:
+    type: OS::Neutron::RouterInterface
+    properties:
+      router: { get_resource: router }
+      port: { get_resource: routerport }
+
+  #Key used to authenticate to instances as root.
+  key:
+    type: OS::Nova::KeyPair
+    properties:
+      name: { get_param: "OS::stack_name" }
+      public_key: { get_param: auth_key }
+  #Handle to signal about starting up of instances.
+  instance_wait_handle:
+    type: OS::Heat::WaitConditionHandle
+  #Monitor waiting for all instances to start.
+  instance_wait:
+    type: OS::Heat::WaitCondition
+    properties:
+      handle: { get_resource: instance_wait_handle }
+      timeout: 1200
+      count:
+        yaql:
+          data: { num_nodes: { get_param: num_nodes } }
+          #This is number of all nodes + 2 (infra instance and installer)
+          expression: "$.data.num_nodes + 2"
+  #Resource group to deploy n nodes using node template for each, each node numbered starting from 0.
+  nodes:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: { get_param: num_nodes }
+      resource_def:
+        type: node.yaml
+        properties:
+          nodenum: "%index%"
+          key_name: { get_resource: key }
+          image_name: { get_param: image_name }
+          network: { get_resource: privnet }
+          subnet: { get_resource: privsubnet }
+          flavor_name: { get_param: node_flavor_name }
+          notify_command: { get_attr: ["instance_wait_handle", "curl_cli"] }
+          security_group: { get_resource: secgroup }
+    depends_on: [routercon, instance_wait_handle]
+  #Nfs storage volume for first node.
+  nfs_storage:
+    type: OS::Cinder::Volume
+    properties:
+      name: nfs_storage
+      size: 50
+  #Attachment of volume to first node.
+  nfs_storage_attachment:
+    type: OS::Cinder::VolumeAttachment
+    properties:
+      instance_uuid: { get_attr: [nodes, "resource.0"] }
+      volume_id: { get_resource: nfs_storage }
+  #Floating ip association for node (first only).
+  node_fip_assoc:
+    type: OS::Neutron::FloatingIPAssociation
+    properties:
+      floatingip_id: { get_param: node_ip }
+      port_id: { get_attr: ["nodes", "resource.0.port_id"] }
+  #Openstack volume used for storing resources.
+  resources_storage:
+    type: "OS::Cinder::Volume"
+    properties:
+      name: "resources_storage"
+      size: 120
+  #Instance representing infrastructure instance, created using subtemplate.
+  infra:
+    type: "instance.yaml"
+    properties:
+      instance_name: infra
+      network: { get_resource: privnet }
+      subnet: { get_resource: privsubnet }
+      key_name: { get_resource: key }
+      flavor_name: { get_param: infra_flavor_name }
+      image_name: { get_param: image_name }
+      notify_command: { get_attr: ["instance_wait_handle", "curl_cli"] }
+      security_group: { get_resource: secgroup }
+    depends_on: [instance_wait_handle]
+  #Volume attachment for infra node.
+  resources_storage_attachment:
+    type: OS::Cinder::VolumeAttachment
+    properties:
+      volume_id: { get_resource: resources_storage }
+      instance_uuid: { get_resource: infra }
+  #Floating ip association for infra.
+  infra_fip_assoc:
+    type: OS::Neutron::FloatingIPAssociation
+    properties:
+      floatingip_id: { get_param: infra_ip }
+      port_id: { get_attr: ["infra", "port_id"] }
+  #Small installer vm having access to other instances, used to install onap.
+  installer:
+    type: "instance.yaml"
+    properties:
+      instance_name: installer
+      image_name: { get_param: image_name }
+      flavor_name: { get_param: installer_flavor_name }
+      key_name: { get_resource: key }
+      network: { get_resource: privnet }
+      subnet: { get_resource: privsubnet }
+      notify_command: { get_attr: ["instance_wait_handle", "curl_cli"] }
+      security_group: { get_resource: secgroup }
+    depends_on: instance_wait_handle
+  #Floating ip for installer.
+  installer_fip_assoc:
+    type: OS::Neutron::FloatingIPAssociation
+    properties:
+      floatingip_id: { get_param: installer_ip }
+      port_id: { get_attr: [installer, port_id] }
+#Output values
+outputs:
+  installer_ip:
+    value: { get_attr: [installer, ip] }
+    description: "Internal ip of installer instance"
+  infra_ip:
+    value: { get_attr: [infra, ip] }
+    description: "Internal ip of infra instance"
+  node_ips:
+    value: { get_attr: [nodes, ip] }
+    description: "Serialized json list of node internal ips starting at node0"
+  volumes:
+    description: "map of volumes per each instance"
+    value:
+      yaql:
+        data:
+          resources_volid: { get_resource: resources_storage }
+          nfs_volid: { get_resource: nfs_storage }
+          docker_volids: { get_attr: [nodes, docker_storage_id] }
+        #This is going to create a map, where keys are instance names, and values are lists of
+        #pairs of volume ids and their mount points.
+        #This is done by merging few generated maps together, base map is taken by
+        #enumerating over docker storage volumes and transforming them into a map like
+        #{"node0"=>["volid","/var/lib/docker"],...], node1=>...}
+        expression: 'dict($.data.docker_volids.enumerate().select(["node"+str($[0]), [[$[1], "/var/lib/docker"]]])).mergeWith({"infra" => [[$.data.resources_volid, "/opt/onap"]], "node0" => [[$.data.nfs_volid, "/dockerdata-nfs"]]})'