Initial commit
[ta/infra-ansible.git] / roles / performance_nodes / files / configure_ovsdpdk.py
diff --git a/roles/performance_nodes/files/configure_ovsdpdk.py b/roles/performance_nodes/files/configure_ovsdpdk.py
new file mode 100644 (file)
index 0000000..31e784c
--- /dev/null
@@ -0,0 +1,256 @@
+#!/usr/bin/env python
+
+# Copyright 2019 Nokia
+
+# 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 argparse
+import subprocess
+import sys
+
+
+class CoreHandler(object):
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def hex_to_set(hexstr):
+        hexn = int(hexstr, 16)
+        b = 1
+        cpuset = set()
+        for i in range(0, 64):
+            if hexn & b != 0:
+                cpuset.add(i)
+            b = b << 1
+        return cpuset
+
+    @staticmethod
+    def set_to_hex(cpuset):
+        cpumask = 0
+        for i in cpuset:
+            b = 1 << i
+            cpumask += b
+        return '0x{:x}'.format(cpumask)
+
+    @staticmethod
+    def string_to_set(cpustr):
+        if cpustr == '' or cpustr is None:
+            raise Exception("Empty string")
+        cpuset_ids = set()
+        cpuset_reject_ids = set()
+        for rule in cpustr.split(','):
+            rule = rule.strip()
+            # Handle multi ','
+            if len(rule) < 1:
+                continue
+            # Note the count limit in the .split() call
+            range_parts = rule.split('-', 1)
+            if len(range_parts) > 1:
+                # So, this was a range; start by converting the parts to ints
+                try:
+                    start, end = [int(p.strip()) for p in range_parts]
+                except ValueError:
+                    raise Exception("Invalid range expression {}".format(rule))
+                # Make sure it's a valid range
+                if start > end:
+                    raise Exception("Invalid range expression (start > end): {}".format(rule))
+                # Add available CPU ids to set
+                cpuset_ids |= set(range(start, end + 1))
+            elif rule[0] == '^':
+                # Not a range, the rule is an exclusion rule; convert to int
+                try:
+                    cpuset_reject_ids.add(int(rule[1:].strip()))
+                except ValueError:
+                    raise Exception("Invalid exclusion expression {}".format(rule))
+            else:
+                # OK, a single CPU to include; convert to int
+                try:
+                    cpuset_ids.add(int(rule))
+                except ValueError:
+                    raise Exception("Invalid inclusion expression {}".format(rule))
+
+        # Use sets to handle the exclusion rules for us
+        cpuset_ids -= cpuset_reject_ids
+        return cpuset_ids
+
+    @staticmethod
+    def set_to_string(cpuset):
+        ranges = []
+        previndex = None
+        for cpuindex in sorted(cpuset):
+            if previndex is None or previndex != (cpuindex - 1):
+                ranges.append([])
+            ranges[-1].append(cpuindex)
+            previndex = cpuindex
+
+        parts = []
+        for entry in ranges:
+            if len(entry) == 1:
+                parts.append(str(entry[0]))
+            else:
+                parts.append("{}-{}".format(entry[0], entry[len(entry) - 1]))
+        return ",".join(parts)
+
+    def hex_to_string(self, hexstr):
+        """
+        :param hexstr: CPU mask as hex string
+        :returns: a formatted CPU range string
+        """
+        cpuset = self.hex_to_set(hexstr)
+        return self.set_to_string(cpuset)
+
+    def string_to_hex(self, cpustr):
+        cpuset = self.string_to_set(cpustr)
+        return self.set_to_hex(cpuset)
+
+
+class OvsVsctl(object):
+    def __init__(self, cores, pcore, tcore, sockets):
+        self.cores = cores
+        self.pcore = pcore
+        self.threads_per_core = tcore
+        self.sockets = sockets
+
+    def is_hyperthreading_enabled(self):
+        if int(self.threads_per_core) >= 2:
+            return True
+        return False
+
+    def is_single_socket(self):
+        if int(self.sockets) == 1:
+            return True
+        return False
+
+    @staticmethod
+    def get_value(field):
+        """Get value for specified field in Open_vSwitch other_config.
+        Returns:
+            Value without quotes or newlines
+            None if field is not set
+        """
+        try:
+            current_value = subprocess.check_output(["ovs-vsctl", "get", "Open_vSwitch", ".", "other_config:{}".format(field)])
+        except Exception:
+            return None
+        return current_value.lstrip('\"').strip('\n').rstrip('\"')
+
+    def set_pmd_cpu_mask(self, value):
+        """Set DPDK core mask."""
+        current_value = self.get_value('pmd-cpu-mask')
+        print "INFO: New core mask {}, current_value {}".format(value, current_value)
+        if current_value == value:
+            return False
+        try:
+            subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:pmd-cpu-mask=\"{}\"".format(value)])
+        except Exception:
+            sys.exit(2)
+        return True
+
+    def set_lcore_mask(self):
+        """Set DPDK library mask."""
+        #if self.is_single_socket():
+        mask = '0x0'
+        # TODO: measure if it would be beneficial to reserve more cores and from second socket
+        current_value = self.get_value('dpdk-lcore-mask')
+        print "INFO: Mask {},  current_value {}".format(mask, current_value)
+
+        if current_value == mask:
+            return False
+        try:
+            subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:dpdk-lcore-mask=\"{}\"".format(mask)])
+        except Exception:
+            sys.exit(2)
+        return True
+
+    def set_socket_mem(self, value):
+        """Set DPDK memory."""
+        if self.is_single_socket():
+            mem = '{0},0'.format(value)
+        else:
+            mem = '{0},{0}'.format(value)
+        current_value = self.get_value('dpdk-socket-mem')
+        print "INFO: New mem {},  current_value {}".format(mem, current_value)
+
+        if current_value == mem:
+            return False
+        try:
+            subprocess.check_output(["ovs-vsctl", "set", "Open_vSwitch", ".", "other_config:dpdk-socket-mem=\"{}\"".format(mem)])
+        except Exception:
+            sys.exit(2)
+        return True
+
+    def enable_dpdk(self):
+        """Enable DPDK."""
+        current_value = self.get_value('dpdk-init')
+        print "INFO: Enable DPDK, current_value {}".format(current_value)
+        if current_value == 'true':
+            return False
+
+        try:
+            with open('/etc/performance-nodes/dpdk.enabled', 'a'):
+                pass
+        except IOError:
+            sys.exit(2)
+
+        return True
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--dpdkcores', dest='dpdkcores', help='DPDK cores', required=False, type=str)
+    parser.add_argument('--cores', dest='cores', help='Logical cores', required=False, type=str)
+    parser.add_argument('--pcore', dest='pcore', help='Cores per processor', required=False, type=str)
+    parser.add_argument('--tcore', dest='tcore', help='Threads per core', required=False, type=str)
+    parser.add_argument('--sockets', dest='sockets', help='Number of sockets', required=False, type=str)
+    parser.add_argument('--mem', dest='mem', help='Socket mem', required=False, type=str, default='4096')
+    args = parser.parse_args()
+
+    ovsvsctl = OvsVsctl(args.cores, args.pcore, args.tcore, args.sockets)
+    coreh = CoreHandler()
+
+    dpdkcoreset = None
+    try:
+        print "INFO: dpdk cores: {}, cpu count: {}".format(args.dpdkcores, args.cores)
+        if args.dpdkcores:
+            dpdkcoreset = coreh.string_to_set(args.dpdkcores)
+    except Exception as exp:
+        print "ERROR: Calculating of cpu/core set failed: {}".format(exp)
+        sys.exit(2)
+
+    if max(dpdkcoreset) > int(args.cores):
+        print "ERROR: invalid DPDK cores (too big)"
+        sys.exit(2)
+
+    # Check if hyperthreading is off and we can calculate new core set so that
+    # DPDK has a chance to start.
+    if not ovsvsctl.is_hyperthreading_enabled() and max(dpdkcoreset) > int(args.cores):
+        print "INFO: Hyperthreading off, DPDK cores contain too big core"
+        dpdkcoreset = set([x for x in dpdkcoreset if x < int(args.cores)])
+        print "INFO: new dpdkcoreset {}".format(dpdkcoreset)
+    else:
+        dpdkcoreset = dpdkcoreset
+    dpdkcoremask = coreh.set_to_hex(dpdkcoreset)
+
+    changed = False
+
+    changed |= ovsvsctl.set_pmd_cpu_mask(dpdkcoremask)
+    #changed |= ovsvsctl.set_lcore_mask()
+    changed |= ovsvsctl.set_socket_mem(args.mem)
+    changed |= ovsvsctl.enable_dpdk()
+    if changed:
+        exit(0)
+    else:
+        exit(1)
+
+if __name__ == "__main__":
+    main()