Merge "Doc: Release notes for Fraser"
[vswitchperf.git] / tools / llc_management / rmd.py
1 # Copyright 2017-2018 Spirent Communications.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """
16 Perform L3-cache allocations for different workloads- VNFs, PMDs, vSwitch etc.
17 based on the user-defined policies. This is done using Intel-RMD.
18 Details about RMD can be found in: https://github.com/intel/rmd
19 """
20
21
22 import itertools
23 import json
24 import logging
25 import math
26 import socket
27
28 from collections import defaultdict
29 from stcrestclient import resthttp
30 from conf import settings as S
31
32 DEFAULT_PORT = 8888
33 DEFAULT_SERVER = '127.0.0.1'
34 DEFAULT_VERSION = 'v1'
35
36
37 def cpumask2coreids(mask):
38     """
39     Convert CPU mask in hex-string to list of core-IDs
40     """
41     intmask = int(mask, 16)
42     i = 1
43     coreids = []
44     while i <= intmask:
45         if i & intmask:
46             coreids.append(str(math.frexp(i)[1] - 1))
47         i = i << 1
48     return coreids
49
50
51 def get_cos(category):
52     """
53     Obtain the Classof service for a particular category
54     """
55     return S.getValue(category.upper() + '_COS')
56
57
58 def get_minmax(category):
59     """
60     Obtain the min-max values for a particular category
61     """
62     return S.getValue(category.upper() + '_CA')
63
64
65 def guest_vm_settings_expanded(cores):
66     """
67     Check if are running pv+p mode
68     """
69     for core in cores:
70         if isinstance(core, str) and '#' in core:
71             return False
72     return True
73
74
75 class IrmdHttp(object):
76     """
77     Intel RMD ReST API wrapper object
78     """
79
80     def __init__(self, server=None, port=None, api_version=None):
81         if not port:
82             server = DEFAULT_SERVER
83         if not port:
84             port = DEFAULT_PORT
85         if not api_version:
86             api_version = DEFAULT_VERSION
87         url = resthttp.RestHttp.url('http', server, port, api_version)
88         rest = resthttp.RestHttp(url, None, None, False, True)
89         try:
90             rest.get_request('workloads')
91         except (socket.error, resthttp.ConnectionError,
92                 resthttp.RestHttpError):
93             raise RuntimeError('Cannot connect to RMD server: %s:%s' %
94                                (server, port))
95         self._rest = rest
96         self.workloadids = []
97         self._logger = logging.getLogger(__name__)
98
99     def setup_cacheways(self, affinity_map):
100         """
101         Sets up the cacheways using RMD apis.
102         """
103         for cos_cat in affinity_map:
104             if S.getValue('POLICY_TYPE') == 'COS':
105                 params = {'core_ids': affinity_map[cos_cat],
106                           'policy': get_cos(cos_cat)}
107             else:
108                 minmax = get_minmax(cos_cat)
109                 if len(minmax) < 2:
110                     return
111                 params = {'core_ids': affinity_map[cos_cat],
112                           'min_cache': minmax[0],
113                           'max_cache': minmax[1]}
114             try:
115                 _, data = self._rest.post_request('workloads', None,
116                                                   params)
117                 if 'id' in data:
118                     wl_id = data['id']
119                     self.workloadids.append(wl_id)
120
121             except resthttp.RestHttpError as exp:
122                 if str(exp).find('already exists') >= 0:
123                     raise RuntimeError("The cacheway already exist")
124                 else:
125                     raise RuntimeError('Failed to connect: ' + str(exp))
126
127     def reset_all_cacheways(self):
128         """
129         Resets the cacheways
130         """
131         try:
132             for wl_id in self.workloadids:
133                 self._rest.delete_request('workloads', str(wl_id))
134         except resthttp.RestHttpError as ecp:
135             raise RuntimeError('Failed to connect: ' + str(ecp))
136
137     def log_allocations(self):
138         """
139         Log the current cacheway settings.
140         """
141         try:
142             _, data = self._rest.get_request('workloads')
143             self._logger.info("Current Allocations: %s",
144                               json.dumps(data, indent=4, sort_keys=True))
145         except resthttp.RestHttpError as ecp:
146             raise RuntimeError('Failed to connect: ' + str(ecp))
147
148
149 class CacheAllocator(object):
150     """
151     This class exposes APIs for VSPERF to perform
152     Cache-allocation management operations.
153     """
154
155     def __init__(self):
156         port = S.getValue('RMD_PORT')
157         api_version = S.getValue('RMD_API_VERSION')
158         server_ip = S.getValue('RMD_SERVER_IP')
159         self.irmd_manager = IrmdHttp(str(server_ip), str(port),
160                                      str(api_version))
161
162     def setup_llc_allocation(self):
163         """
164         Wrapper for settingup cacheways
165         """
166         cpumap = defaultdict(list)
167         vswitchmask = S.getValue('VSWITCHD_DPDK_CONFIG')['dpdk-lcore-mask']
168         vnfcores = list(itertools.chain.from_iterable(
169             S.getValue('GUEST_CORE_BINDING')))
170         if not guest_vm_settings_expanded(vnfcores):
171             vnfcores = None
172         nncores = None
173         if S.getValue('LOADGEN') == 'StressorVM':
174             nncores = list(itertools.chain.from_iterable(
175                 S.getValue('NN_CORE_BINDING')))
176         pmdcores = cpumask2coreids(S.getValue('VSWITCH_PMD_CPU_MASK'))
177         vswitchcores = cpumask2coreids(vswitchmask)
178         if vswitchcores:
179             cpumap['vswitch'] = vswitchcores
180         if vnfcores:
181             cpumap['vnf'] = vnfcores
182         if pmdcores:
183             cpumap['pmd'] = pmdcores
184         if nncores:
185             cpumap['noisevm'] = nncores
186         self.irmd_manager.setup_cacheways(cpumap)
187
188     def cleanup_llc_allocation(self):
189         """
190         Wrapper for cacheway cleanup
191         """
192         self.irmd_manager.reset_all_cacheways()
193
194     def log_allocations(self):
195         """
196         Wrapper for logging cacheway allocations
197         """
198         self.irmd_manager.log_allocations()