Add trait check for POST Device Profile

At present, when creating a device profile, the function
_validate_post_request only checks if the keys of groups
matches that in ["resources:", "trait:", "accel:"] without
checking the string following "trait:","resources:",this
introduces risks.

For example, the string followed by "trait:" default should be
"CUSTOM_", what follows "resources:" should be a valid resource type,
but the fact is that users may make typos such as adding extra spaces
like "resources:  FPGA", or other format errors. This kind of error
cannot be detected by the current _validate_post_request, and it won't
be exposed until nova parses the rc of cyborg.

This is not in compliance with the process specification and potentially
introduces greater risks. Therefore, it is necessary to implement a more
strict inspection of rc in device profile POST API.

This patch added the following,rc check will be followed in the next
patch.
1.Check the legality of traits name
2.Space removal in traits
3.Trait value check

Change-Id: Ie3a2c6340e2b41edd8d943ac9e0bda0a1e504d3c
Story: 2008143
Task: 40884
This commit is contained in:
Yumeng Bao
2020-09-16 23:16:25 +08:00
parent 9a16e9b678
commit 315e147d4d
2 changed files with 67 additions and 9 deletions

View File

@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import pecan
import re
from six.moves import http_client
@@ -133,17 +134,32 @@ class DeviceProfilesController(base.CyborgController,
groups = req_devprof.get("groups")
if not groups:
raise exception.DeviceProfileGroupsExpected()
else:
for group in groups:
for key, value in group.items():
if not re.match(GROUP_KEYS, key):
for group in groups:
tmp_group = copy.deepcopy(group)
for key, value in tmp_group.items():
# check resource and trait prefix format
if not re.match(GROUP_KEYS, key):
raise exception.InvalidParameterValue(
err="Device profile group keys must be of "
" the form %s" % GROUP_KEYS)
# check trait name and it's value
if key.startswith("trait:"):
inner_origin_trait = ":".join(key.split(":")[1:])
inner_trait = inner_origin_trait.strip(" ")
if not inner_trait.startswith('CUSTOM_'):
raise exception.InvalidParameterValue(
err="Device profile group keys must be of "
" the form %s" % GROUP_KEYS)
if key.startswith("trait:") and value not in TRAIT_VALUES:
err="Unsupported trait name format %s, should "
"start with CUSTOM_" % inner_trait)
if value not in TRAIT_VALUES:
raise exception.InvalidParameterValue(
err="Device profile trait values must be one "
"among %s" % TRAIT_VALUES)
err="Unsupported trait value %s, the value must"
" be one among %s" % TRAIT_VALUES)
# strip " " and update old group key.
if inner_origin_trait != inner_trait:
del group[key]
standard_key = "trait:" + inner_trait
group[standard_key] = value
def _get_device_profile_list(self, names=None, uuid=None):
"""Get a list of API objects representing device profiles."""

View File

@@ -15,6 +15,7 @@
from six.moves import http_client
from unittest import mock
import webtest
from oslo_serialization import jsonutils
@@ -82,6 +83,47 @@ class TestDeviceProfileController(v2_test.APITestV2):
self.assertEqual(http_client.CREATED, response.status_int)
self._validate_dp(dp[0], out_dp)
def test_create_with_unsupported_trait(self):
test_unsupport_dp = self.fake_dps[0]
# generate special trait for test
del test_unsupport_dp['groups'][0][
'trait:CUSTOM_FPGA_INTEL_PAC_ARRIA10']
test_unsupport_dp['groups'][0]['trait:FAKE_TRAIT'] = 'required'
dp = [test_unsupport_dp]
dp[0]['created_at'] = str(dp[0]['created_at'])
self.assertRaisesRegex(
webtest.app.AppError,
".*Unsupported trait name format FAKE_TRAIT.*",
self.post_json,
self.DP_URL,
dp,
headers=self.headers)
@mock.patch('cyborg.conductor.rpcapi.ConductorAPI.device_profile_create')
def test_create_with_extra_space_in_trait(self, mock_cond_dp):
test_unsupport_dp = self.fake_dps[0]
# generate a requested dp which has extra space in trait
del test_unsupport_dp['groups'][0][
'trait:CUSTOM_FPGA_INTEL_PAC_ARRIA10']
test_unsupport_dp['groups'][0][
'trait: CUSTOM_FPGA_INTEL_PAC_ARRIA10'] = 'required'
dp = [test_unsupport_dp]
mock_cond_dp.return_value = self.fake_dp_objs[0]
dp[0]['created_at'] = str(dp[0]['created_at'])
response = self.post_json(self.DP_URL, dp, headers=self.headers)
out_dp = jsonutils.loads(response.controller_output)
# check that the extra space in trait:
# {'trait: CUSTOM_FPGA_INTEL_PAC_ARRIA10': 'required'} is
# successful stripped by the _validate_post_request function, and
# the created device_profile has no extra space in trait
# {'trait:CUSTOM_FPGA_INTEL_PAC_ARRIA10': 'required}
self.assertTrue(out_dp['groups'] == self.fake_dp_objs[0]['groups'])
@mock.patch('cyborg.conductor.rpcapi.ConductorAPI.device_profile_delete')
@mock.patch('cyborg.objects.DeviceProfile.get_by_name')
@mock.patch('cyborg.objects.DeviceProfile.get_by_uuid')