From 2b118f08ac25cb1763be5d503587ef655633ce7b Mon Sep 17 00:00:00 2001
From: Javier Diaz <jdiaz@mirantis.com>
Date: Thu, 22 Sep 2022 14:07:41 -0500
Subject: [PATCH 1/2] Configure ARM formula

---
 formulas/arm/configure.sls                    | 384 +++++++++++++++++-
 formulas/arm/files/10-iommu.cfg               |   1 +
 formulas/arm/files/arp_protect.py             | 236 +++++++++++
 formulas/arm/files/ceph-nova.xml              |   6 +
 formulas/arm/files/ceph-volumes.xml           |   6 +
 formulas/arm/files/config                     |   1 +
 formulas/arm/files/hosts                      |   3 +
 formulas/arm/files/kvm.conf                   |   1 +
 formulas/arm/files/linuxbridge_agent.ini      |  17 +
 formulas/arm/files/neutron.conf               |  45 ++
 .../arm/files/neutron_ovn_metadata_agent.ini  |  23 ++
 formulas/arm/files/neutron_sudoers            |   5 +
 formulas/arm/files/nova.conf                  | 184 +++++++++
 formulas/arm/files/openvswitch_agent.ini      |  22 +
 formulas/arm/install.sls                      |  62 ++-
 formulas/common/base.sls                      |   4 +
 16 files changed, 997 insertions(+), 3 deletions(-)
 create mode 100644 formulas/arm/files/10-iommu.cfg
 create mode 100644 formulas/arm/files/arp_protect.py
 create mode 100644 formulas/arm/files/ceph-nova.xml
 create mode 100644 formulas/arm/files/ceph-volumes.xml
 create mode 100644 formulas/arm/files/config
 create mode 100644 formulas/arm/files/hosts
 create mode 100644 formulas/arm/files/kvm.conf
 create mode 100644 formulas/arm/files/linuxbridge_agent.ini
 create mode 100644 formulas/arm/files/neutron.conf
 create mode 100644 formulas/arm/files/neutron_ovn_metadata_agent.ini
 create mode 100644 formulas/arm/files/neutron_sudoers
 create mode 100644 formulas/arm/files/nova.conf
 create mode 100644 formulas/arm/files/openvswitch_agent.ini

diff --git a/formulas/arm/configure.sls b/formulas/arm/configure.sls
index 30c7d581..32fef4c5 100644
--- a/formulas/arm/configure.sls
+++ b/formulas/arm/configure.sls
@@ -1,2 +1,384 @@
+## Copyright 2018 Augusta University
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+##    http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
 include:
-  - /formulas/compute/configure
+  - /formulas/{{ grains['role'] }}/install
+  - /formulas/common/ceph/configure
+
+{% set type = opts.id.split('-')[0] %}
+{% set role = salt['pillar.get']('hosts:'+type+':role', type) %}
+
+{% import 'formulas/common/macros/constructor.sls' as constructor with context %}
+
+### Calculate public interface - this is referenced a few times
+### Potential to make 'interface calculator' macro
+{% if salt['pillar.get']('hosts:'+grains['type']+':networks:public:bridge', False) == True %}
+  {% set public_interface = 'public_br' %}
+{% elif salt['pillar.get']('hosts:'+grains['type']+':networks:public:interfaces') | length > 1 %}
+  {% set public_interface = 'public_bond' %}
+{% else %}
+  {% set public_interface = pillar['hosts'][grains['type']]['networks']['public']['interfaces'][0] %}
+{% endif %}
+
+{% set nova_uuid = pillar['ceph']['nova-uuid'] %}
+{% set volumes_uuid = pillar['ceph']['volumes-uuid'] %}
+
+
+#/etc/frr/daemons:
+#  file.managed:
+#    - source: salt://formulas/common/frr/files/daemons
+
+
+conf-files:
+  file.managed:
+    - template: jinja
+    - defaults:
+        nova_uuid: {{ nova_uuid }}
+        volumes_uuid: {{ volumes_uuid }}
+        transport_url: {{ constructor.rabbitmq_url_constructor() }}
+        www_authenticate_uri: {{ constructor.endpoint_url_constructor(project='keystone', service='keystone', endpoint='public') }}
+{% if salt['pillar.get']('hosts:barbican:enabled', 'False') == True %}
+        barbican_endpoint: {{ constructor.endpoint_url_constructor(project='barbican', service='barbican', endpoint='internal') }}
+{% endif %}
+        auth_url: {{ constructor.endpoint_url_constructor(project='keystone', service='keystone', endpoint='internal') }}
+        memcached_servers: {{ constructor.memcached_url_constructor() }}
+        nova_password: {{ pillar['nova']['nova_service_password'] }}
+        my_ip: {{ salt['network.ipaddrs'](cidr=pillar['networking']['subnets']['management'])[0] }}
+        api_servers: {{ constructor.endpoint_url_constructor(project='glance', service='glance', endpoint='internal') }}
+        neutron_password: {{ pillar['neutron']['neutron_service_password'] }}
+        placement_password: {{ pillar['placement']['placement_service_password'] }}
+        rbd_secret_uuid: {{ pillar['ceph']['nova-uuid'] }}
+        token_ttl: {{ pillar['nova']['token_ttl'] }}
+        console_domain: {{ pillar['haproxy']['console_domain'] }}
+        dashboard_domain: {{ pillar['haproxy']['dashboard_domain'] }}
+        initial_cpu_allocation_ratio : {{ pillar['hosts'][grains['type']]['initial_cpu_allocation_ratio'] | default(1.0) }}
+        initial_ram_allocation_ratio : {{ pillar['hosts'][grains['type']]['initial_ram_allocation_ratio'] | default(1.5) }}
+        initial_disk_allocation_ratio : {{ pillar['hosts'][grains['type']]['initial_disk_allocation_ratio'] | default(1.0) }}
+  {% if grains['os_family'] == 'Debian' %}
+        lock_path: /var/lock/neutron
+  {% elif grains['os_family'] == 'RedHat' %}
+        lock_path: /var/lib/neutron/tmp
+  {% endif %}
+    - names:
+      - /etc/modprobe.d/kvm.conf:
+        - source: salt://formulas/compute/files/kvm.conf
+#      - /etc/frr/daemons:
+#        - source: salt://formulas/common/frr/files/daemons
+      - /etc/ceph/ceph-nova.xml:
+        - source: salt://formulas/compute/files/ceph-nova.xml
+      - /etc/ceph/ceph-volumes.xml:
+        - source: salt://formulas/compute/files/ceph-volumes.xml
+      - /etc/nova/nova.conf:
+        - source: salt://formulas/compute/files/nova.conf
+      - /etc/sudoers.d/neutron_sudoers:
+        - source: salt://formulas/compute/files/neutron_sudoers
+      - /etc/neutron/neutron.conf:
+        - source: salt://formulas/compute/files/neutron.conf
+
+
+compute_hosts:
+  file.managed:
+    - template: jinja
+    - defaults:
+        compute_hosts: {{ constructor.host_file_constructor(role='compute')|yaml_encode }}
+    - names:
+      - /etc/hosts:
+        - source: salt://formulas/compute/files/hosts
+
+ceph_keyrings:
+  file.managed:
+    - names:
+      - /etc/ceph/ceph.client.compute.keyring:
+        - contents_pillar: ceph:ceph-client-compute-keyring
+      - /etc/ceph/client.compute.key:
+        - contents_pillar: ceph:ceph-client-compute-key
+      - /etc/ceph/client.volumes.key:
+        - contents_pillar: ceph:ceph-client-volumes-key
+    - mode: "0640"
+    - user: root
+    - group: nova
+
+libvirt_secrets:
+  file.managed:
+    - makedirs: True
+    - template: jinja
+    - defaults:
+        nova_uuid: {{ nova_uuid }}
+        volumes_uuid: {{ volumes_uuid }}
+    - names:
+      - /etc/libvirt/secrets/{{ nova_uuid }}.base64:
+        - contents_pillar: ceph:ceph-client-compute-key
+      - /etc/libvirt/secrets/{{ volumes_uuid }}.base64:
+        - contents_pillar: ceph:ceph-client-volumes-key
+      - /etc/libvirt/secrets/{{ nova_uuid }}.xml:
+        - source: salt://formulas/compute/files/ceph-nova.xml
+      - /etc/libvirt/secrets/{{ volumes_uuid }}.xml:
+        - source: salt://formulas/compute/files/ceph-volumes.xml
+    - mode: "0600"
+    - user: root
+    - group: root
+
+/root/.ssh/config:
+  file.managed:
+    - user: root
+    - group: root
+    - mode: '0400'
+    - source: salt://formulas/compute/files/config
+
+{% for key in pillar['nova_live_migration_auth_key'] %}
+{{ key }}:
+  ssh_auth.present:
+    - user: root
+    - enc: {{ pillar['nova_live_migration_auth_key'][ key ]['encoding'] }}
+{% endfor %}
+
+/root/.ssh/id_rsa:
+  file.managed:
+    - contents_pillar: nova_private_key
+    - user: root
+    - group: root
+    - mode: '0600'
+    - replace: False
+
+{% if grains['os_family'] == 'RedHat' %}
+spice-html5:
+  git.latest:
+    - name: https://github.com/freedesktop/spice-html5.git
+    - target: /usr/share/spice-html5
+{% endif %}
+
+nova_compute_service:
+  service.running:
+{% if grains['os_family'] == 'Debian' %}
+    - name: nova-compute
+{% elif grains['os_family'] == 'RedHat' %}
+    - name: openstack-nova-compute
+{% endif %}
+    - enable: true
+    - watch:
+      - file: /etc/nova/nova.conf
+
+{% set neutron_backend = pillar['neutron']['backend'] %}
+{% if neutron_backend != "networking-ovn" %}
+/etc/neutron/plugins/ml2/{{ neutron_backend }}_agent.ini:
+  file.managed:
+    - source: salt://formulas/compute/files/{{ neutron_backend }}_agent.ini
+    - template: jinja
+    - defaults:
+        local_ip: {{ salt['network.ip_addrs'](cidr=pillar['networking']['subnets']['private'])[0] }}
+        public_interface: {{ public_interface }}
+  {% if neutron_backend == "openvswitch" %}
+        extensions: qos
+        bridge_mappings: public_br
+        vxlan_udp_port: 4789
+        l2_population: True
+        arp_responder: True
+        enable_distributed_routing: False
+        drop_flows_on_start: False
+        explicitly_egress_direct: True
+
+create_bridge:
+  openvswitch_bridge.present:
+    - name: public_br
+
+create_port:
+  openvswitch_port.present:
+    - name: {{ public_interface }}
+    - bridge: public_br
+  {% endif %}
+  {% if grains['os_family'] == 'RedHat' %}
+    {% if (salt['grains.get']('selinux:enabled', False) == True) and (salt['grains.get']('selinux:enforced', 'Permissive') == 'Enforcing')  %}
+## this used to be a default but was changed to a boolean here:
+## https://github.com/redhat-openstack/openstack-selinux/commit/9cfdb0f0aa681d57ca52948f632ce679d9e1f465
+os_neutron_dac_override:
+  selinux.boolean:
+    - value: on
+    - persist: True
+    - watch_in:
+      - service: neutron_{{ neutron_backend }}_agent_service
+    {% endif %}
+  {% endif %}
+
+neutron_{{ neutron_backend }}_agent_service:
+  service.running:
+    - name: neutron-{{ neutron_backend }}-agent
+    - enable: true
+    - watch:
+      - file: conf-files
+      - file: /etc/neutron/plugins/ml2/{{ neutron_backend }}_agent.ini
+
+{% elif neutron_backend == "networking-ovn" %}
+neutron-ovn-metadata-agent.ini:
+  file.managed:
+    - source: salt://formulas/compute/files/neutron_ovn_metadata_agent.ini
+    - name: /etc/neutron/neutron_ovn_metadata_agent.ini
+    - template: jinja
+    - defaults:
+        nova_metadata_host: {{ pillar['endpoints']['public'] }}
+        metadata_proxy_shared_secret: {{ pillar['neutron']['metadata_proxy_shared_secret'] }}
+        ovn_sb_connection: {{ constructor.ovn_sb_connection_constructor() }}
+
+openvswitch_service:
+  service.running:
+  {% if grains['os_family'] == 'Debian' %}
+    - name: openvswitch-switch
+  {% elif grains['os_family'] == 'RedHat' %}
+    - name: openvswitch
+  {% endif %}
+    - enable: true
+    - watch:
+      - file: conf-files
+      - file: neutron-ovn-metadata-agent.ini
+
+set-ovn-remote:
+  cmd.run:
+    - name: ovs-vsctl set open . external-ids:ovn-remote={{ constructor.ovn_sb_connection_constructor() }}
+    - require:
+      - service: openvswitch_service
+    - unless:
+      - ovs-vsctl get open . external-ids:ovn-remote | grep -q "{{ constructor.ovn_sb_connection_constructor() }}"
+
+set_encap:
+  cmd.run:
+    - name: ovs-vsctl set open . external-ids:ovn-encap-type=geneve
+    - require:
+      - service: openvswitch_service
+    - unless:
+      - ovs-vsctl get open . external-ids:ovn-encap-type | grep -q "geneve"
+
+set_encap_ip:
+  cmd.run:
+    - name: ovs-vsctl set open . external-ids:ovn-encap-ip={{ salt['network.ip_addrs'](cidr=pillar['networking']['subnets']['private'])[0] }}
+    - require:
+      - service: openvswitch_service
+      - cmd: set_encap
+    - unless:
+      - ovs-vsctl get open . external-ids:ovn-encap-ip | grep -q "{{ salt['network.ip_addrs'](cidr=pillar['networking']['subnets']['private'])[0] }}"
+
+make_bridge:
+  cmd.run:
+    - name: ovs-vsctl --may-exist add-br br-provider -- set bridge br-provider protocols=OpenFlow13
+    - require:
+      - service: openvswitch_service
+      - cmd: set_encap
+      - cmd: set_encap_ip
+    - unless:
+      - ovs-vsctl br-exists br-provider
+
+map_bridge:
+  cmd.run:
+    - name: ovs-vsctl set open . external-ids:ovn-bridge-mappings=provider:br-provider
+    - require:
+      - service: openvswitch_service
+      - cmd: set_encap
+      - cmd: set_encap_ip
+      - cmd: make_bridge
+    - unless:
+      - ovs-vsctl get open . external-ids:ovn-bridge-mappings | grep -q "provider:br-provider"
+
+ovs-vsctl set open . external_ids:ovn-remote-probe-interval=180000 :
+  cmd.run:
+    - require:
+      - service: openvswitch_service
+    - retry:
+        attempts: 3
+        interval: 10
+        splay: 5
+    - unless:
+      - ovs-vsctl get open . external-ids:ovn-remote-probe-interval | grep -q "180000"
+
+ovs-vsctl set open . external_ids:ovn-openflow-probe-interval=180 :
+  cmd.run:
+    - require:
+      - service: openvswitch_service
+    - retry:
+        attempts: 3
+        interval: 10
+        splay: 5
+    - unless:
+      - ovs-vsctl get open . external-ids:ovn-openflow-probe-interval | grep -q "180"
+
+ovsdb_listen:
+  cmd.run:
+    - name: ovs-vsctl set-manager ptcp:6640:127.0.0.1
+    - unless:
+      - ovs-vsctl get-manager | grep -q "ptcp:6640:127.0.0.1"
+
+enable_bridge:
+  cmd.run:
+    - name: ovs-vsctl --may-exist add-port br-provider {{ public_interface }}
+    - require:
+      - service: openvswitch_service
+      - cmd: set_encap
+      - cmd: set_encap_ip
+      - cmd: make_bridge
+      - cmd: map_bridge
+    - unless:
+      - ovs-vsctl port-to-br {{ public_interface }} | grep -q "br-provider"
+
+ovn_controller_service:
+  service.running:
+  {% if grains['os_family'] == 'Debian' %}
+    - name: ovn-host
+  {% elif grains['os_family'] == 'RedHat' %}
+    - name: ovn-controller
+  {% endif %}
+    - enable: true
+    - require:
+      - service: openvswitch_service
+      - cmd: set_encap
+      - cmd: set_encap_ip
+
+ovn_metadata_service:
+  service.running:
+    - name: neutron-ovn-metadata-agent
+    - enable: True
+    - watch:
+      - file: neutron-ovn-metadata-agent.ini
+    - require:
+      - cmd: ovsdb_listen
+{% endif %}
+
+libvirtd_service:
+  service.running:
+    - name: libvirtd
+    - enable: True
+    - watch:
+      - file: /etc/libvirt/secrets/{{ nova_uuid }}.xml
+      - file: /etc/libvirt/secrets/{{ volumes_uuid }}.xml
+      - file: /etc/libvirt/secrets/{{ nova_uuid }}.base64
+      - file: /etc/libvirt/secrets/{{ volumes_uuid }}.base64
+    - require:
+      - file: libvirt_secrets
+
+/etc/default/grub.d/10-iommu.cfg:
+  file.managed:
+    - user: root
+    - group: root
+    - mode: '0644'
+    - template: jinja
+    - source: salt://formulas/compute/files/10-iommu.cfg
+    - defaults:
+        kernel_param: {{ pillar['hosts'][grains['type']]['kernel_param'][0] }}
+
+update-grub:
+  cmd.run:
+    - onchanges:
+      - file: /etc/default/grub.d/10-iommu.cfg
+
+system.reboot:
+  module.run:
+    - onchanges:
+      - file: /etc/default/grub.d/10-iommu.cfg
+
diff --git a/formulas/arm/files/10-iommu.cfg b/formulas/arm/files/10-iommu.cfg
new file mode 100644
index 00000000..28265c04
--- /dev/null
+++ b/formulas/arm/files/10-iommu.cfg
@@ -0,0 +1 @@
+GRUB_CMDLINE_LINUX_DEFAULT="{{ kernel_param }}"
\ No newline at end of file
diff --git a/formulas/arm/files/arp_protect.py b/formulas/arm/files/arp_protect.py
new file mode 100644
index 00000000..6ed3f7ed
--- /dev/null
+++ b/formulas/arm/files/arp_protect.py
@@ -0,0 +1,236 @@
+# Copyright (c) 2015 Mirantis, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import netaddr
+from neutron_lib.utils import net
+from oslo_concurrency import lockutils
+from oslo_log import log as logging
+import tenacity
+
+from neutron.agent.linux import ip_lib
+
+LOG = logging.getLogger(__name__)
+SPOOF_CHAIN_PREFIX = 'neutronARP-'
+MAC_CHAIN_PREFIX = 'neutronMAC-'
+
+
+def setup_arp_spoofing_protection(vif, port_details):
+    if not port_details.get('port_security_enabled', True):
+        # clear any previous entries related to this port
+        delete_arp_spoofing_protection([vif])
+        LOG.info("Skipping ARP spoofing rules for port '%s' because "
+                 "it has port security disabled", vif)
+        return
+    if net.is_port_trusted(port_details):
+        # clear any previous entries related to this port
+        delete_arp_spoofing_protection([vif])
+        LOG.debug("Skipping ARP spoofing rules for network owned port "
+                  "'%s'.", vif)
+        return
+    _setup_arp_spoofing_protection(vif, port_details)
+
+
+@lockutils.synchronized('ebtables')
+def _setup_arp_spoofing_protection(vif, port_details):
+    current_rules = ebtables(['-L']).splitlines()
+    _install_mac_spoofing_protection(vif, port_details, current_rules)
+    # collect all of the addresses and cidrs that belong to the port
+    addresses = {f['ip_address'] for f in port_details['fixed_ips']}
+    if port_details.get('allowed_address_pairs'):
+        addresses |= {p['ip_address']
+                      for p in port_details['allowed_address_pairs']}
+
+    addresses = {ip for ip in addresses
+                 if netaddr.IPNetwork(ip).version == 4}
+    if any(netaddr.IPNetwork(ip).prefixlen == 0 for ip in addresses):
+        # don't try to install protection because a /0 prefix allows any
+        # address anyway and the ARP_SPA can only match on /1 or more.
+        return
+
+    _install_arp_spoofing_protection(vif, addresses, current_rules)
+
+
+def chain_name(vif):
+    # start each chain with a common identifier for cleanup to find
+    return '%s%s' % (SPOOF_CHAIN_PREFIX, vif)
+
+
+@lockutils.synchronized('ebtables')
+def delete_arp_spoofing_protection(vifs):
+    current_rules = ebtables(['-L']).splitlines()
+    _delete_arp_spoofing_protection(vifs, current_rules, table='nat',
+                                    chain='PREROUTING')
+
+    # TODO(haleyb) this can go away in "R" cycle, it's here to cleanup
+    # old chains in the filter table
+    current_rules = ebtables(['-L'], table='filter').splitlines()
+    _delete_arp_spoofing_protection(vifs, current_rules, table='filter',
+                                    chain='FORWARD')
+
+
+def _delete_arp_spoofing_protection(vifs, current_rules, table, chain):
+    # delete the jump rule and then delete the whole chain
+    jumps = [vif for vif in vifs if vif_jump_present(vif, current_rules)]
+    for vif in jumps:
+        ebtables(['-D', chain, '-i', vif, '-j',
+                  chain_name(vif), '-p', 'ARP'], table=table)
+    for vif in vifs:
+        chain_delete(chain_name(vif), table, current_rules)
+    _delete_mac_spoofing_protection(vifs, current_rules, table=table,
+                                    chain=chain)
+
+
+def _delete_unreferenced_arp_protection(current_vifs, table, chain):
+    # deletes all jump rules and chains that aren't in current_vifs but match
+    # the spoof prefix
+    current_rules = ebtables(['-L'], table=table).splitlines()
+    to_delete = []
+    for line in current_rules:
+        # we're looking to find and turn the following:
+        # Bridge chain: SPOOF_CHAIN_PREFIXtap199, entries: 0, policy: DROP
+        # into 'tap199'
+        if line.startswith('Bridge chain: %s' % SPOOF_CHAIN_PREFIX):
+            devname = line.split(SPOOF_CHAIN_PREFIX, 1)[1].split(',')[0]
+            if devname not in current_vifs:
+                to_delete.append(devname)
+    LOG.info("Clearing orphaned ARP spoofing entries for devices %s",
+             to_delete)
+    _delete_arp_spoofing_protection(to_delete, current_rules, table=table,
+                                    chain=chain)
+
+
+@lockutils.synchronized('ebtables')
+def delete_unreferenced_arp_protection(current_vifs):
+    _delete_unreferenced_arp_protection(current_vifs,
+                                        table='nat', chain='PREROUTING')
+
+    # TODO(haleyb) this can go away in "R" cycle, it's here to cleanup
+    # old chains in the filter table
+    _delete_unreferenced_arp_protection(current_vifs,
+                                        table='filter', chain='FORWARD')
+
+
+@lockutils.synchronized('ebtables')
+def install_arp_spoofing_protection(vif, addresses):
+    current_rules = ebtables(['-L']).splitlines()
+    _install_arp_spoofing_protection(vif, addresses, current_rules)
+
+
+def _install_arp_spoofing_protection(vif, addresses, current_rules):
+    # make a VIF-specific ARP chain so we don't conflict with other rules
+    vif_chain = chain_name(vif)
+    if not chain_exists(vif_chain, current_rules):
+        ebtables(['-N', vif_chain, '-P', 'DROP'])
+    # flush the chain to clear previous accepts. this will cause dropped ARP
+    # packets until the allows are installed, but that's better than leaked
+    # spoofed packets and ARP can handle losses.
+    ebtables(['-F', vif_chain])
+    for addr in sorted(addresses):
+        ebtables(['-A', vif_chain, '-p', 'ARP', '--arp-ip-src', addr,
+                  '-j', 'ACCEPT'])
+    # check if jump rule already exists, if not, install it
+    if not vif_jump_present(vif, current_rules):
+        ebtables(['-A', 'PREROUTING', '-i', vif, '-j',
+                  vif_chain, '-p', 'ARP'])
+
+
+def chain_exists(chain, current_rules):
+    for rule in current_rules:
+        if rule.startswith('Bridge chain: %s' % chain):
+            return True
+    return False
+
+
+def chain_delete(chain, table, current_rules):
+    # flush and delete chain if exists
+    if chain_exists(chain, current_rules):
+        ebtables(['-F', chain], table=table)
+        ebtables(['-X', chain], table=table)
+
+
+def vif_jump_present(vif, current_rules):
+    searches = (('-i %s' % vif), ('-j %s' % chain_name(vif)), ('-p ARP'))
+    for line in current_rules:
+        if all(s in line for s in searches):
+            return True
+    return False
+
+
+def _install_mac_spoofing_protection(vif, port_details, current_rules):
+    mac_addresses = {port_details['mac_address']}
+    if port_details.get('allowed_address_pairs'):
+        mac_addresses |= {p['mac_address']
+                          for p in port_details['allowed_address_pairs']}
+    mac_addresses = list(mac_addresses)
+    vif_chain = _mac_chain_name(vif)
+    # mac filter chain for each vif which has a default deny
+    if not chain_exists(vif_chain, current_rules):
+        ebtables(['-N', vif_chain, '-P', 'DROP'])
+    # check if jump rule already exists, if not, install it
+    if not _mac_vif_jump_present(vif, current_rules):
+        ebtables(['-A', 'PREROUTING', '-i', vif, '-j', vif_chain])
+    # we can't just feed all allowed macs at once because we can exceed
+    # the maximum argument size. limit to 500 per rule.
+    for chunk in (mac_addresses[i:i + 500]
+                  for i in range(0, len(mac_addresses), 500)):
+        new_rule = ['-A', vif_chain, '-i', vif,
+                    '--among-src', ','.join(sorted(chunk)), '-j', 'RETURN']
+        ebtables(new_rule)
+    _delete_vif_mac_rules(vif, current_rules)
+
+
+def _mac_vif_jump_present(vif, current_rules):
+    searches = (('-i %s' % vif), ('-j %s' % _mac_chain_name(vif)))
+    for line in current_rules:
+        if all(s in line for s in searches):
+            return True
+    return False
+
+
+def _mac_chain_name(vif):
+    return '%s%s' % (MAC_CHAIN_PREFIX, vif)
+
+
+def _delete_vif_mac_rules(vif, current_rules):
+    chain = _mac_chain_name(vif)
+    for rule in current_rules:
+        if '-i %s' % vif in rule and '--among-src' in rule:
+            ebtables(['-D', chain] + rule.split())
+
+
+def _delete_mac_spoofing_protection(vifs, current_rules, table, chain):
+    # delete the jump rule and then delete the whole chain
+    jumps = [vif for vif in vifs
+             if _mac_vif_jump_present(vif, current_rules)]
+    for vif in jumps:
+        ebtables(['-D', chain, '-i', vif, '-j',
+                  _mac_chain_name(vif)], table=table)
+    for vif in vifs:
+        chain_delete(_mac_chain_name(vif), table, current_rules)
+
+
+# Used to scope ebtables commands in testing
+NAMESPACE = None
+
+
+@tenacity.retry(
+    wait=tenacity.wait_exponential(multiplier=0.02),
+    retry=tenacity.retry_if_exception(lambda e: e.returncode == 255),
+    reraise=True
+)
+def ebtables(comm, table='nat'):
+    execute = ip_lib.IPWrapper(NAMESPACE).netns.execute
+    return execute(['ebtables', '-t', table, '--concurrent'] + comm,
+                   run_as_root=True)
diff --git a/formulas/arm/files/ceph-nova.xml b/formulas/arm/files/ceph-nova.xml
new file mode 100644
index 00000000..78d9fa87
--- /dev/null
+++ b/formulas/arm/files/ceph-nova.xml
@@ -0,0 +1,6 @@
+<secret ephemeral="no" private="no">
+<uuid>{{ nova_uuid }}</uuid>
+<usage type="ceph">
+<name>client.compute secret</name>
+</usage>
+</secret>
diff --git a/formulas/arm/files/ceph-volumes.xml b/formulas/arm/files/ceph-volumes.xml
new file mode 100644
index 00000000..e4da0afc
--- /dev/null
+++ b/formulas/arm/files/ceph-volumes.xml
@@ -0,0 +1,6 @@
+<secret ephemeral="no" private="no">
+<uuid>{{ volumes_uuid }}</uuid>
+<usage type="ceph">
+<name>client.volumes secret</name>
+</usage>
+</secret>
diff --git a/formulas/arm/files/config b/formulas/arm/files/config
new file mode 100644
index 00000000..b34c69cb
--- /dev/null
+++ b/formulas/arm/files/config
@@ -0,0 +1 @@
+StrictHostKeyChecking no
\ No newline at end of file
diff --git a/formulas/arm/files/hosts b/formulas/arm/files/hosts
new file mode 100644
index 00000000..7a742350
--- /dev/null
+++ b/formulas/arm/files/hosts
@@ -0,0 +1,3 @@
+127.0.0.1           localhost
+
+{{ compute_hosts }}
\ No newline at end of file
diff --git a/formulas/arm/files/kvm.conf b/formulas/arm/files/kvm.conf
new file mode 100644
index 00000000..3d7921fa
--- /dev/null
+++ b/formulas/arm/files/kvm.conf
@@ -0,0 +1 @@
+options kvm ignore_msrs=1
\ No newline at end of file
diff --git a/formulas/arm/files/linuxbridge_agent.ini b/formulas/arm/files/linuxbridge_agent.ini
new file mode 100644
index 00000000..f3948192
--- /dev/null
+++ b/formulas/arm/files/linuxbridge_agent.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+
+[agent]
+
+[linux_bridge]
+physical_interface_mappings = provider:{{ public_interface }}
+
+[network_log]
+
+[securitygroup]
+enable_security_group = True
+firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
+
+[vxlan]
+enable_vxlan = True
+local_ip = {{ local_ip }}
+l2_population = True
diff --git a/formulas/arm/files/neutron.conf b/formulas/arm/files/neutron.conf
new file mode 100644
index 00000000..e3c7b15a
--- /dev/null
+++ b/formulas/arm/files/neutron.conf
@@ -0,0 +1,45 @@
+[DEFAULT]
+core_plugin = ml2
+transport_url = {{ transport_url }}
+auth_strategy = keystone
+use_syslog = False
+rpc_conn_pool_size = 300
+executor_thread_pool_size = 2048
+rpc_response_timeout = 3600
+rpc_workers = 5
+report_interval = 120
+
+[agent]
+root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
+root_helper_daemon = sudo /usr/bin/neutron-rootwrap-daemon /etc/neutron/rootwrap.conf
+
+[cors]
+[cors.subdomain]
+[database]
+
+[keystone_authtoken]
+www_authenticate_uri = {{ www_authenticate_uri }}
+auth_url = {{ auth_url }}
+memcached_servers = {{ memcached_servers }}
+auth_type = password
+project_domain_name = Default
+user_domain_name = Default
+project_name = service
+username = neutron
+password = {{ neutron_password }}
+service_token_roles = admin
+service_token_roles_required = True
+
+[matchmaker_redis]
+[nova]
+[oslo_concurrency]
+lock_path = {{ lock_path }}
+
+[oslo_messaging_amqp]
+[oslo_messaging_notifications]
+[oslo_messaging_rabbit]
+[oslo_messaging_zmq]
+[oslo_policy]
+[qos]
+[quotas]
+[ssl]
diff --git a/formulas/arm/files/neutron_ovn_metadata_agent.ini b/formulas/arm/files/neutron_ovn_metadata_agent.ini
new file mode 100644
index 00000000..277576b1
--- /dev/null
+++ b/formulas/arm/files/neutron_ovn_metadata_agent.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+nova_metadata_host = {{ nova_metadata_host }}
+metadata_proxy_shared_secret = {{ metadata_proxy_shared_secret }}
+nova_metadata_protocol = https
+use_syslog = False
+### Added this per https://bugs.launchpad.net/neutron/+bug/1893656
+### and great feedback from redhat
+metadata_workers = 2
+
+[AGENT]
+root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
+root_helper_daemon = sudo /usr/bin/neutron-rootwrap-daemon /etc/neutron/rootwrap.conf
+
+[cache]
+
+[ovs]
+ovsdb_connection = tcp:127.0.0.1:6640
+
+[ovn]
+ovn_sb_connection = {{ ovn_sb_connection }}
+
+[privsep]
+helper_command = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf privsep-helper --config-file /etc/neutron/neutron_ovn_metadata_agent.ini
diff --git a/formulas/arm/files/neutron_sudoers b/formulas/arm/files/neutron_sudoers
new file mode 100644
index 00000000..0abb34bc
--- /dev/null
+++ b/formulas/arm/files/neutron_sudoers
@@ -0,0 +1,5 @@
+Defaults:neutron !requiretty
+Defaults use_pty
+Defaults logfile="/var/log/sudo.log"
+
+neutron ALL = (root) NOPASSWD: /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf *,/usr/bin/neutron-rootwrap-daemon /etc/neutron/rootwrap.conf
diff --git a/formulas/arm/files/nova.conf b/formulas/arm/files/nova.conf
new file mode 100644
index 00000000..910e9aa1
--- /dev/null
+++ b/formulas/arm/files/nova.conf
@@ -0,0 +1,184 @@
+[DEFAULT]
+lock_path = /var/lock/nova
+state_path = /var/lib/nova
+use_neutron = True
+firewall_driver = nova.virt.firewall.NoopFirewallDriver
+my_ip = {{ my_ip }}
+transport_url = {{ transport_url }}
+vnc_enabled = false
+web=/usr/share/spice-html5
+block_device_allocate_retries_interval = 10
+block_device_allocate_retries = 120
+resume_guests_state_on_host_boot = True
+use_syslog = False
+reserved_host_memory_mb=8196
+timeout_nbd = 60
+heal_instance_info_cache_interval = 600
+vif_plugging_timeout = 600
+rpc_response_timeout = 3600
+
+
+# Weighting Options
+
+initial_cpu_allocation_ratio = {{ initial_cpu_allocation_ratio }}
+initial_ram_allocation_ratio = {{ initial_ram_allocation_ratio }}
+initial_disk_allocation_ratio = {{ initial_disk_allocation_ratio }}
+
+[api]
+auth_strategy = keystone
+
+[api_database]
+
+{% if salt['pillar.get']('hosts:barbican:enabled', 'False') == True %}
+[barbican]
+barbican_endpoint = {{ barbican_endpoint }}
+auth_endpoint = {{ auth_url }}
+barbican_endpoint_type = internal
+{% endif %}
+
+[cache]
+enabled = True
+memcache_servers = {{ memcached_servers }}
+backend = oslo_cache.memcache_pool
+
+[cells]
+enable=False
+
+[cinder]
+
+[compute]
+consecutive_build_service_disable_threshold = 0
+
+[conductor]
+[console]
+[consoleauth]
+[cors]
+[crypto]
+[database]
+[ephemeral_storage_encryption]
+[filter_scheduler]
+enabled_filters=AggregateMultiTenancyIsolation,AggregateImagePropertiesIsolation,AggregateInstanceExtraSpecsFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter
+[glance]
+api_servers = {{ api_servers }}
+
+[guestfs]
+[healthcheck]
+[hyperv]
+[image_file_url]
+[ironic]
+[key_manager]
+[keystone]
+
+[keystone_authtoken]
+www_authenticate_uri = {{ www_authenticate_uri }}
+auth_url = {{ auth_url }}
+memcached_servers = {{ memcached_servers }}
+auth_type = password
+project_domain_name = Default
+user_domain_name = Default
+project_name = service
+username = nova
+password = {{ nova_password }}
+service_token_roles = admin
+service_token_roles_required = True
+
+[libvirt]
+use_virtio_for_bridges = True
+images_rbd_pool = vms
+images_type = rbd
+rbd_secret_uuid = {{ rbd_secret_uuid }}
+rbd_user = compute
+disk_cachemodes="network=writeback"
+live_migration_retry_count = 10
+max_concurrent_live_migrations = 10
+live_migration_bandwidth = 0
+live_migration_uri = qemu+ssh://%s/system?no_verify=1
+
+[matchmaker_redis]
+[metrics]
+[mks]
+
+[neutron]
+auth_url = {{ auth_url }}
+auth_type = password
+project_domain_name = Default
+user_domain_name = Default
+project_name = service
+region_name = RegionOne
+username = neutron
+password = {{ neutron_password }}
+
+[notifications]
+[osapi_v21]
+
+[oslo_concurrency]
+lock_path = /var/lib/nova/tmp
+
+[oslo_messaging_amqp]
+connection_retry_interval_max = 60
+default_reply_timeout = 60
+default_send_timeout = 60
+default_notify_timeout = 60
+
+[oslo_messaging_kafka]
+[oslo_messaging_notifications]
+[oslo_messaging_rabbit]
+[oslo_messaging_zmq]
+[oslo_middleware]
+[oslo_policy]
+enforce_new_defaults=False
+enforce_scope=False
+
+[pci]
+{% if pillar['gpu']['backend'] == "pci-passthrough" %}
+alias = { "vendor_id":"10de", "product_id":"1db6", "device_type":"type-PCI", "name":"TeslaV100" }
+passthrough_whitelist = { "vendor_id": "10de", "product_id": "1db6" }
+
+{% endif %}
+[placement]
+os_region_name = RegionOne
+auth_url = {{ auth_url }}
+auth_type = password
+project_domain_name = Default
+user_domain_name = Default
+project_name = service
+username = placement
+password = {{ placement_password }}
+
+[quota]
+[rdp]
+[remote_debug]
+[scheduler]
+[serial_console]
+enabled = True	
+port_range = 10000:20000	
+base_url = wss://{{ dashboard_domain }}:6083/	
+proxyclient_address = $my_ip
+[service_user]
+
+[spice]
+enabled = True
+agent_enabled = True
+html5proxy_base_url = https://{{ console_domain }}/spice_auto.html
+server_proxyclient_address = $my_ip
+server_listen = 0.0.0.0
+
+[upgrade_levels]
+[vault]
+[vendordata_dynamic_auth]
+[vmware]
+
+[vnc]
+enabled = False
+novncproxy_base_url = https://{{ console_domain }}/vnc_auto.html
+server_listen = 0.0.0.0
+server_proxyclient_address = $my_ip
+
+[workarounds]
+### related to https://bugs.launchpad.net/nova/+bug/1946729
+### config guide https://docs.openstack.org/nova/latest/configuration/config.html#:~:text=wait_for_vif_plugged_event_during_hard_reboot
+wait_for_vif_plugged_event_during_hard_reboot = normal
+
+[wsgi]
+[xenserver]
+[xvp]
diff --git a/formulas/arm/files/openvswitch_agent.ini b/formulas/arm/files/openvswitch_agent.ini
new file mode 100644
index 00000000..155d5c69
--- /dev/null
+++ b/formulas/arm/files/openvswitch_agent.ini
@@ -0,0 +1,22 @@
+[DEFAULT]
+
+[agent]
+tunnel_types = vxlan
+vxlan_udp_port = {{ vxlan_udp_port }}
+l2_population = {{ l2_population }}
+arp_responder = {{ arp_responder }}
+enable_distributed_routing = {{ enable_distributed_routing }}
+drop_flows_on_start = {{ drop_flows_on_start }}
+extensions = {{ extensions }}
+explicitly_egress_direct = {{ explicitly_egress_direct }}
+
+[ovs]
+local_ip = {{ local_ip }}
+bridge_mappings = provider:{{ bridge_mappings }}
+of_connect_timeout = 300
+of_request_timeout = 300
+
+[securitygroup]
+firewall_driver = openvswitch
+enable_security_group = True
+enable_ipset = true
diff --git a/formulas/arm/install.sls b/formulas/arm/install.sls
index bca65e1c..13594baf 100644
--- a/formulas/arm/install.sls
+++ b/formulas/arm/install.sls
@@ -1,4 +1,4 @@
-## Copyright 2021 United States Army Cyber School
+## Copyright 2018 Augusta University
 ##
 ## Licensed under the Apache License, Version 2.0 (the "License");
 ## you may not use this file except in compliance with the License.
@@ -13,4 +13,62 @@
 ## limitations under the License.
 
 include:
-  - /formulas/compute/install
+  - /formulas/common/base
+  - /formulas/common/networking
+  - /formulas/common/install
+  - /formulas/common/openstack/repo
+  - /formulas/common/ceph/repo
+  - /formulas/common/frr/repo
+
+{% if grains['os_family'] == 'Debian' %}
+compute_packages:
+  pkg.installed:
+    - pkgs:
+      - nova-compute
+      - python3-tornado
+      - ceph-common
+      - spice-html5
+      - python3-rbd
+      - python3-rados
+#      - frr
+#      - frr-pythontools
+      - python3-etcd3gw
+      - qemu-system
+      - nvme-cli
+  {% if pillar['neutron']['backend'] == "linuxbridge" %}
+      - neutron-linuxbridge-agent
+  {% elif pillar['neutron']['backend'] == "openvswitch" %}
+      - neutron-openvswitch-agent
+  {% elif pillar['neutron']['backend'] == "networking-ovn" %}
+      - ovn-host
+      - neutron-ovn-metadata-agent
+      - haproxy
+  {% endif %}
+
+{% elif grains['os_family'] == 'RedHat' %}
+compute_packages:
+  pkg.installed:
+    - pkgs:
+      - openstack-nova-compute
+      - python3-tornado
+      - ceph-common
+      - python3-rbd
+      - python3-rados
+      - conntrack-tools
+      - frr
+      - frr-pythontools
+      - qemu-system-arm
+      - qemu-system-mips
+      - nvme-cli
+  {% if pillar['neutron']['backend'] == "linuxbridge" %}
+      - openstack-neutron-linuxbridge
+  {% elif pillar['neutron']['backend'] == "openvswitch" %}
+      - openstack-openvswitch-agent
+   {% elif pillar['neutron']['backend'] == "networking-ovn" %}
+      - rdo-ovn-host
+      - openstack-neutron-ovn-metadata-agent
+      - openstack-neutron-common
+      - haproxy
+  {% endif %}
+
+{% endif %}
diff --git a/formulas/common/base.sls b/formulas/common/base.sls
index b0745c67..d4a5d5db 100644
--- a/formulas/common/base.sls
+++ b/formulas/common/base.sls
@@ -191,6 +191,8 @@ cpufrequtils.service:
       - file: /etc/default/cpufrequtils
 {% endif %}
 
+{% if role != 'arm' %}
+
 /etc/udev/rules.d/60-netdev.rules:
   file.managed:
     - user: root
@@ -198,6 +200,8 @@ cpufrequtils.service:
     - mode: '0600'
     - source: salt://formulas/common/kernel/files/60-netdev.rules
 
+{% endif %}
+
 # The LDAP server is not controlled under this project. Pointing to the source so if it ever changes this will still work
 /usr/local/share/ca-certificates/ipa.cybbh.space/ldap_ca.crt:
   file.managed:
-- 
GitLab


From 98dcd14087055e4f8933ab4cccdbf1c9b496eeeb Mon Sep 17 00:00:00 2001
From: Javier Diaz <jdiaz@mirantis.com>
Date: Thu, 22 Sep 2022 14:10:01 -0500
Subject: [PATCH 2/2] Edit file locations

---
 formulas/arm/configure.sls | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/formulas/arm/configure.sls b/formulas/arm/configure.sls
index 32fef4c5..d0309c24 100644
--- a/formulas/arm/configure.sls
+++ b/formulas/arm/configure.sls
@@ -72,19 +72,19 @@ conf-files:
   {% endif %}
     - names:
       - /etc/modprobe.d/kvm.conf:
-        - source: salt://formulas/compute/files/kvm.conf
+        - source: salt://formulas/arm/files/kvm.conf
 #      - /etc/frr/daemons:
 #        - source: salt://formulas/common/frr/files/daemons
       - /etc/ceph/ceph-nova.xml:
-        - source: salt://formulas/compute/files/ceph-nova.xml
+        - source: salt://formulas/arm/files/ceph-nova.xml
       - /etc/ceph/ceph-volumes.xml:
-        - source: salt://formulas/compute/files/ceph-volumes.xml
+        - source: salt://formulas/arm/files/ceph-volumes.xml
       - /etc/nova/nova.conf:
-        - source: salt://formulas/compute/files/nova.conf
+        - source: salt://formulas/arm/files/nova.conf
       - /etc/sudoers.d/neutron_sudoers:
-        - source: salt://formulas/compute/files/neutron_sudoers
+        - source: salt://formulas/arm/files/neutron_sudoers
       - /etc/neutron/neutron.conf:
-        - source: salt://formulas/compute/files/neutron.conf
+        - source: salt://formulas/arm/files/neutron.conf
 
 
 compute_hosts:
@@ -94,7 +94,7 @@ compute_hosts:
         compute_hosts: {{ constructor.host_file_constructor(role='compute')|yaml_encode }}
     - names:
       - /etc/hosts:
-        - source: salt://formulas/compute/files/hosts
+        - source: salt://formulas/arm/files/hosts
 
 ceph_keyrings:
   file.managed:
@@ -122,9 +122,9 @@ libvirt_secrets:
       - /etc/libvirt/secrets/{{ volumes_uuid }}.base64:
         - contents_pillar: ceph:ceph-client-volumes-key
       - /etc/libvirt/secrets/{{ nova_uuid }}.xml:
-        - source: salt://formulas/compute/files/ceph-nova.xml
+        - source: salt://formulas/arm/files/ceph-nova.xml
       - /etc/libvirt/secrets/{{ volumes_uuid }}.xml:
-        - source: salt://formulas/compute/files/ceph-volumes.xml
+        - source: salt://formulas/arm/files/ceph-volumes.xml
     - mode: "0600"
     - user: root
     - group: root
@@ -134,7 +134,7 @@ libvirt_secrets:
     - user: root
     - group: root
     - mode: '0400'
-    - source: salt://formulas/compute/files/config
+    - source: salt://formulas/arm/files/config
 
 {% for key in pillar['nova_live_migration_auth_key'] %}
 {{ key }}:
@@ -173,7 +173,7 @@ nova_compute_service:
 {% if neutron_backend != "networking-ovn" %}
 /etc/neutron/plugins/ml2/{{ neutron_backend }}_agent.ini:
   file.managed:
-    - source: salt://formulas/compute/files/{{ neutron_backend }}_agent.ini
+    - source: salt://formulas/arm/files/{{ neutron_backend }}_agent.ini
     - template: jinja
     - defaults:
         local_ip: {{ salt['network.ip_addrs'](cidr=pillar['networking']['subnets']['private'])[0] }}
@@ -221,7 +221,7 @@ neutron_{{ neutron_backend }}_agent_service:
 {% elif neutron_backend == "networking-ovn" %}
 neutron-ovn-metadata-agent.ini:
   file.managed:
-    - source: salt://formulas/compute/files/neutron_ovn_metadata_agent.ini
+    - source: salt://formulas/arm/files/neutron_ovn_metadata_agent.ini
     - name: /etc/neutron/neutron_ovn_metadata_agent.ini
     - template: jinja
     - defaults:
@@ -368,7 +368,7 @@ libvirtd_service:
     - group: root
     - mode: '0644'
     - template: jinja
-    - source: salt://formulas/compute/files/10-iommu.cfg
+    - source: salt://formulas/arm/files/10-iommu.cfg
     - defaults:
         kernel_param: {{ pillar['hosts'][grains['type']]['kernel_param'][0] }}
 
-- 
GitLab