dns/py/params.py (190 lines of code) (raw):

#!/usr/bin/env python3 # Copyright 2016 The Kubernetes Authors. # # 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. """ The test parameter space explored by the performance test is defined in this module. Each Param below represents a tunable used to configure the test runs. Add additional parameters to the test by subclassing *Param class and adding them to the `PARAMETERS` global variable. """ import copy import logging import re import time import yaml _log = logging.getLogger(__name__) class Inputs(object): """ Inputs to the dns performance test. """ def __init__(self, deployment_yaml, configmap_yaml, dnsperf_cmdline): self.deployment_yaml = copy.deepcopy(deployment_yaml) self.configmap_yaml = copy.deepcopy(configmap_yaml) self.dnsperf_cmdline = dnsperf_cmdline class Param(object): """ A test parameter. """ def __init__(self, name, data_type): """ |name| of the parameter (e.g. 'kubedns_cpu') |data_type| of the parameter value (e.g. `int`) |attributes| optional, associated with a parameter. Is a `set` of str. """ self.name = name self.data_type = data_type def is_relevant(self, attributes): #pylint: disable=no-self-use,unused-argument """ Determine whether or not this parameter is relevant to the current test case run. |attributes| set of string attributes. |return| True if this parameter is relevant given the test run attributes. """ return True def set(self, inputs, value): """ Modify the inputs according the value given. |inputs| is of type Inputs. |value| to assign """ raise NotImplementedError() def __repr__(self): return '<%s>' % self.name class DeploymentContainerSpecParam(Param): """ Parameterizes a field in a deployment container specification. """ def __init__(self, name, data_type, container_name, yaml_path): super(DeploymentContainerSpecParam, self).__init__(name, data_type) self.yaml_path = yaml_path self.container_name = container_name def is_relevant(self, attributes): return 'cluster-dns' not in attributes and 'node-local-dns' not in attributes def set(self, inputs, value): spec = _item_by_predicate( inputs.deployment_yaml['spec']['template']['spec']['containers'], lambda x: x['name'] == self.container_name) _set_or_remove(spec, self.yaml_path, value) class KubednsCPU(DeploymentContainerSpecParam): def __init__(self): super(KubednsCPU, self).__init__( 'kubedns_cpu', int, 'kubedns', ['resources', 'limits', 'cpu']) def set(self, inputs, value): super(KubednsCPU, self).set( inputs, None if value is None else '%dm' % value) class CorednsCPU(DeploymentContainerSpecParam): def __init__(self): super(CorednsCPU, self).__init__( 'coredns_cpu', int, 'coredns', ['resources', 'limits', 'cpu']) def set(self, inputs, value): super(CorednsCPU, self).set( inputs, None if value is None else '%dm' % value) class DnsmasqCPU(DeploymentContainerSpecParam): def __init__(self): super(DnsmasqCPU, self).__init__( 'dnsmasq_cpu', int, 'dnsmasq', ['resources', 'limits', 'cpu']) def set(self, inputs, value): super(DnsmasqCPU, self).set( inputs, None if value is None else '%dm' % value) class DnsmasqCache(Param): """ Changes the command line for dnsmasq. """ def __init__(self): super(DnsmasqCache, self).__init__('dnsmasq_cache', int) def is_relevant(self, attributes): return 'cluster-dns' not in attributes def set(self, inputs, value): spec = _item_by_predicate( inputs.deployment_yaml['spec']['template']['spec']['containers'], lambda x: x['name'] == 'dnsmasq') args = spec['args'] args = [x for x in args if not x.startswith('--cache-size')] args.append('--cache-size=%d' % value) spec['args'] = args class CorednsCache(Param): """ Changes the cache setting in the CoreDNS configmap. """ def __init__(self): super(CorednsCache, self).__init__('coredns_cache', int) def is_relevant(self, attributes): return 'cluster-dns' not in attributes def set(self, inputs, value): if value > 0: cf = inputs.configmap_yaml['data']['Corefile'] cfList = cf.decode().split("\n") cfList.insert(1, " cache {\n" " success " + repr(value) + "\n" " denial " + repr(value) + "\n" " }") inputs.configmap_yaml['data']['Corefile'] = "\n".join(cfList) class DnsperfCmdlineParam(Param): """ Parameterizes a field from the dnsperf command line |use_equals| Add the command line param as ['key=value'], rather than ['key', 'value']. """ def __init__(self, name, data_type, option_name, use_equals): super(DnsperfCmdlineParam, self).__init__(name, data_type) self._option_name = option_name self._use_equals = use_equals def set(self, inputs, value): if value is None: return if self._use_equals: inputs.dnsperf_cmdline.append('%s=%s' % self._option_name, value) else: inputs.dnsperf_cmdline.append(self._option_name) inputs.dnsperf_cmdline.append(str(value)) class QueryFile(DnsperfCmdlineParam): def __init__(self): super(QueryFile, self).__init__('query_file', str, '-d', False) def set(self, inputs, value): super(QueryFile, self).set( inputs, None if value is None else '/queries/' + value) class RunLengthSeconds(DnsperfCmdlineParam): def __init__(self): super(RunLengthSeconds, self).__init__( 'run_length_seconds', str, '-l', False) class MaxQPS(DnsperfCmdlineParam): def __init__(self): super(MaxQPS, self).__init__('max_qps', str, '-Q', False) class TestCase(object): def __init__(self, run_id, run_subid, pod_name, pv): self.run_id = run_id self.run_subid = run_subid self.pod_name = pod_name self.pv = pv def __repr__(self): return str(vars(self)) def to_yaml(self): fields = { 'run_id': self.run_id, 'run_subid': self.run_subid, 'pod_name': self.pod_name, } for param, value in self.pv: fields[param.name] = value return fields def configure(self, inputs): """ Generate the right set of inputs to the test run. """ for param, value in self.pv: param.set(inputs, value) class TestCases(object): """ Parameters to range over for the peformance test. """ @staticmethod def load_from_file(filename): """ |filename| yaml file to read from. """ raw = yaml.safe_load(open(filename, 'r')) return TestCases(raw) def __init__(self, values): self.values = values def generate(self, attributes): """ |return| a list of TestCases to run. """ cases = [] run_id = int(time.time()) def iterate(remaining, pv): if len(remaining) == 0: run_subid = len(cases) return cases.append(TestCase(run_id, run_subid, "", pv)) param = remaining[0] if param.name not in self.values or \ not param.is_relevant(attributes): iterate(remaining[1:], pv) return for value in self.values[param.name]: iterate(remaining[1:], pv + [(param, value)]) iterate(PARAMETERS, []) return cases def set_param(self, param_name, values): if param_name not in self.values: return self.values[param_name].append(values) def get_param(self, param_name): if param_name not in self.values: return None return self.values[param_name] def _item_by_predicate(list_obj, predicate): """ Iterate through list_obj and return the object that matches the predicate. |list_obj| list |predicate| f(x) -> bool |return| object otherwise None if not found """ for x in list_obj: if predicate(x): return x return None def _set_or_remove(root, path, value): """ |root| JSON-style object |path| path to modify |value| if not None, value to set, otherwise remove. """ if value is not None: for label in path[:-1]: if label not in root: root[label] = {} root = root[label] root[path[-1]] = value else: for label in path[:-1]: if label not in root: return root = root[label] if path[-1] in root: del root[path[-1]] # Test parameters available for the performance test. # # Note: this should be sorted in order with most disruptive to least # disruptive (disruptive = requires daemon restarts) as this is the # iteration order used to run the perf tests. PARAMETERS = [ RunLengthSeconds(), DnsmasqCPU(), DnsmasqCache(), CorednsCache(), KubednsCPU(), CorednsCPU(), MaxQPS(), QueryFile(), ] # Given as an attribute to TestCases.generate, specifies that the test # case is run with cluster-dns. ATTRIBUTE_CLUSTER_DNS = 'cluster-dns' # specifies that the test uses node-cache ATTRIBUTE_NODELOCAL_DNS = 'node-local-dns'