Fix validate error when k8s resource init

Since the k8s version has been upgraded from v22.6.0 to v23.3.0,
the initial value of some fields must be the specified string,
which causes the initialization of the current k8s resource to fail.

This patch fixes the issue and skips the validation of k8s object
initialization by setting the `client_side_validation` parameter.
In order to pass the validation, the must_param variable was added
to set the initialized value. This modification skips the
validation and no longer uses the variable, so the related processing
of the variable is deleted. At the same time, even if the
initialization validation is skipped, the fields in the yaml file
will still be verified when the create method is called, and an
exception will be thrown and output to the log if the fields are
missing.

At the same time, due to the rapid iteration of the k8s version,
the must_param variable needs to be repaired in each iteration to
adapt to the new validation, which requires repeated maintenance
and high maintenance costs, so it is no longer used.

In kubernetes v23.3.0, 'available_replicas' must be set into status
of `StatefulSet`, so this patch add a kuryr-kubernetes versioned
parameter temporarily to .zuul.yaml to make the response returned
by kubernetes correct.

Closes-Bug: #1968103
Change-Id: I9495ce0f0893e5f9a1d6c52b98c3db3928bd95a3
This commit is contained in:
Yi Feng 2022-04-08 11:43:34 +09:00
parent acf84a168a
commit c52343a974
6 changed files with 42 additions and 190 deletions

View File

@ -439,6 +439,11 @@
KURYR_K8S_API_URL: "https://{{ hostvars['controller-k8s']['nodepool']['private_ipv4'] }}:${KURYR_K8S_API_PORT}"
KURYR_K8S_CONTAINERIZED_DEPLOYMENT: false
KURYR_NEUTRON_DEFAULT_SUBNETPOOL_ID: shared-default-subnetpool-v4
# TODO(YiFeng): At present, the version of kubernetes should be 1.23.3, and the returned response can
# pass the verification of kubernetes-client (1.23.3). This configuration will be removed after
# kuryr-kubernetes fixes the following bug.
# https://bugs.launchpad.net/kuryr-kubernetes/+bug/1968960
KURYR_KUBERNETES_VERSION: 1.23.3
MYSQL_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
OCTAVIA_AMP_IMAGE_FILE: "/tmp/test-only-amphora-x64-haproxy-ubuntu-bionic.qcow2"
OCTAVIA_AMP_IMAGE_NAME: "test-only-amphora-x64-haproxy-ubuntu-bionic"

View File

@ -290,7 +290,7 @@ def fake_pc():
def fake_persistent_volume(
name='curry-sc-pv', phase='UnAvailable'):
name='curry-sc-pv', phase='Pending'):
return client.V1PersistentVolume(
api_version='v1',
kind='PersistentVolume',
@ -395,6 +395,8 @@ def fake_rq():
def fake_stateful_set(ready_replicas=0):
client_config = client.Configuration.get_default_copy()
client_config.client_side_validation = False
return client.V1StatefulSet(
api_version='apps/v1',
kind='StatefulSet',
@ -424,7 +426,8 @@ def fake_stateful_set(ready_replicas=0):
),
status=client.V1StatefulSetStatus(
replicas=2,
ready_replicas=ready_replicas
ready_replicas=ready_replicas,
local_vars_configuration=client_config
),
)

View File

@ -845,7 +845,7 @@ class TestKubernetes(base.BaseTestCase):
mock_node.return_value = fakes.fake_node(status='False')
mock_read_node.side_effect = [
fakes.fake_node(type='UnReady'),
fakes.fake_node(type='NetworkUnavailable'),
fakes.fake_node(), fakes.fake_none()]
self._normal_execute_procedure(req)

View File

@ -238,7 +238,7 @@ def fake_pvc_false():
name='curry-sc-pvc'
),
status=client.V1PersistentVolumeClaimStatus(
phase='UnBound'
phase='Pending'
)
)
@ -286,7 +286,7 @@ def fake_namespace_false():
name='curry-ns'
),
status=client.V1NamespaceStatus(
phase='NotActive'
phase='Terminating'
)
)
@ -658,6 +658,8 @@ def fake_v1_volume_attachment_error():
def fake_v1_stateful_set():
client_config = client.Configuration.get_default_copy()
client_config.client_side_validation = False
return client.V1StatefulSet(
api_version='apps/v1',
kind='StatefulSet',
@ -687,12 +689,15 @@ def fake_v1_stateful_set():
),
status=client.V1StatefulSetStatus(
replicas=1,
ready_replicas=1
ready_replicas=1,
local_vars_configuration=client_config
),
)
def fake_v1_stateful_set_error():
client_config = client.Configuration.get_default_copy()
client_config.client_side_validation = False
return client.V1StatefulSet(
api_version='apps/v1',
kind='StatefulSet',
@ -722,7 +727,8 @@ def fake_v1_stateful_set_error():
),
status=client.V1StatefulSetStatus(
replicas=2,
ready_replicas=1
ready_replicas=1,
local_vars_configuration=client_config
)
)
@ -750,7 +756,7 @@ def fake_v1_persistent_volume_claim_error():
namespace='curryns'
),
status=client.V1PersistentVolumeClaimStatus(
phase='Bound1'
phase='Pending'
)
)
@ -800,7 +806,7 @@ def fake_pod_error():
namespace='curryns'
),
status=client.V1PodStatus(
phase='Terminated',
phase='Failed',
)
)
@ -850,7 +856,7 @@ def fake_persistent_volume_error():
namespace='curryns'
),
status=client.V1PersistentVolumeStatus(
phase='UnBound',
phase='Pending',
)
)
@ -1017,7 +1023,7 @@ def fake_pod_list():
name="fake-name"
),
status=client.V1PodStatus(
phase="Successed"
phase="Succeeded"
)
)]
)

View File

@ -684,7 +684,6 @@ class TestTransformer(base.TestCase):
self.assertEqual(k8s_obj.api_version, 'v1')
# V1LimitRangeSpec
self.assertIsNotNone(k8s_obj.spec.limits)
self.assertIsNotNone(k8s_obj.spec.limits[0].type)
def test_pod_template(self):
k8s_objs = self.transfromer.get_k8s_objs_from_yaml(

View File

@ -161,168 +161,11 @@ class Transformer(object):
return kubernetes_objects
def _create_k8s_object(self, kind, file_content_dict):
# must_param referring K8s official object page
# *e.g:https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Service.md
# initiating k8s object, you need to
# give the must param an empty value.
must_param = {
'V1LocalSubjectAccessReview': '(spec="")',
'V1HTTPGetAction': '(port="")',
'V1DeploymentSpec': '(selector="", template="")',
'V1PodSpec': '(containers=[])',
'V1ConfigMapKeySelector': '(key="")',
'V1Container': '(name="")',
'V1EnvVar': '(name="")',
'V1SecretKeySelector': '(key="")',
'V1ContainerPort': '(container_port="")',
'V1VolumeMount': '(mount_path="", name="")',
'V1PodCondition': '(status="", type="")',
'V1ContainerStatus': '('
'image="", image_id="", '
'name="", ready="", '
'restart_count="")',
'V1ServicePort': '(port=80)',
'V1TypedLocalObjectReference': '(kind="", name="")',
'V1LabelSelectorRequirement': '(key="", operator="")',
'V1PersistentVolumeClaimCondition': '(status="", type="")',
'V1AWSElasticBlockStoreVolumeSource': '(volume_id="")',
'V1AzureDiskVolumeSource': '(disk_name="", disk_uri="")',
'V1AzureFileVolumeSource': '(secret_name="", share_name="")',
'V1CephFSVolumeSource': '(monitors=[])',
'V1CinderVolumeSource': '(volume_id="")',
'V1KeyToPath': '(key="", path="")',
'V1CSIVolumeSource': '(driver="")',
'V1DownwardAPIVolumeFile': '(path="")',
'V1ObjectFieldSelector': '(field_path="")',
'V1ResourceFieldSelector': '(resource="")',
'V1FlexVolumeSource': '(driver="")',
'V1GCEPersistentDiskVolumeSource': '(pd_name="")',
'V1GitRepoVolumeSource': '(repository="")',
'V1GlusterfsVolumeSource': '(endpoints="", path="")',
'V1HostPathVolumeSource': '(path="")',
'V1ISCSIVolumeSource': '(iqn="", lun=0, target_portal="")',
'V1Volume': '(name="")',
'V1NFSVolumeSource': '(path="", server="")',
'V1PersistentVolumeClaimVolumeSource': '(claim_name="")',
'V1PhotonPersistentDiskVolumeSource': '(pd_id="")',
'V1PortworxVolumeSource': '(volume_id="")',
'V1ProjectedVolumeSource': '(sources=[])',
'V1ServiceAccountTokenProjection': '(path="")',
'V1QuobyteVolumeSource': '(registry="", volume="")',
'V1RBDVolumeSource': '(image="", monitors=[])',
'V1ScaleIOVolumeSource': '('
'gateway="", secret_ref="", '
'system="")',
'V1VsphereVirtualDiskVolumeSource': '(volume_path="")',
'V1LimitRangeSpec': '(limits=[])',
'V1Binding': '(target="")',
'V1ComponentCondition': '(status="", type="")',
'V1NamespaceCondition': '(status="", type="")',
'V1ConfigMapNodeConfigSource': '(kubelet_config_key="", '
'name="", namespace="")',
'V1Taint': '(effect="", key="")',
'V1NodeAddress': '(address="", type="")',
'V1NodeCondition': '(status="", type="")',
'V1DaemonEndpoint': '(port=0)',
'V1ContainerImage': '(names=[])',
'V1NodeSystemInfo': '(architecture="", boot_id="", '
'container_runtime_version="",'
'kernel_version="", '
'kube_proxy_version="", '
'kubelet_version="",'
'machine_id="", operating_system="", '
'os_image="", system_uuid="")',
'V1AttachedVolume': '(device_path="", name="")',
'V1ScopedResourceSelectorRequirement':
'(operator="", scope_name="")',
'V1APIServiceSpec': '(group_priority_minimum=0, '
'service="", '
'version_priority=0)',
'V1APIServiceCondition': '(status="", type="")',
'V1DaemonSetSpec': '(selector="", template="")',
'V1ReplicaSetSpec': '(selector="")',
'V1StatefulSetSpec': '(selector="", '
'service_name="", template="")',
'V1StatefulSetCondition': '(status="", type="")',
'V1StatefulSetStatus': '(replicas=0)',
'V1ControllerRevision': '(revision=0)',
'V1TokenReview': '(spec="")',
'V1SubjectAccessReviewStatus': '(allowed=True)',
'V1SelfSubjectAccessReview': '(spec="")',
'V1SelfSubjectRulesReview': '(spec="")',
'V1SubjectRulesReviewStatus': '(incomplete=True, '
'non_resource_rules=[], '
'resource_rules=[])',
'V1NonResourceRule': '(verbs=[])',
'V1SubjectAccessReview': '(spec="")',
'V1HorizontalPodAutoscalerSpec':
'(max_replicas=0, scale_target_ref="")',
'V1CrossVersionObjectReference': '(kind="", name="")',
'V1HorizontalPodAutoscalerStatus':
'(current_replicas=0, desired_replicas=0)',
'V1JobSpec': '(template="")',
'V1NetworkPolicySpec': '(pod_selector="")',
'V1PolicyRule': '(verbs=[])',
'V1ClusterRoleBinding': '(role_ref="")',
'V1RoleRef': '(api_group="", kind="", name="")',
'V1Subject': '(kind="", name="")',
'V1RoleBinding': '(role_ref="")',
'V1PriorityClass': '(value=0)',
'V1StorageClass': '(provisioner="")',
'V1TopologySelectorLabelRequirement': '(key="", values=[])',
'V1VolumeAttachment': '(spec="")',
'V1VolumeAttachmentSpec':
'(attacher="", node_name="", source="")',
'V1VolumeAttachmentStatus': '(attached=True)',
'V1NodeSelector': '(node_selector_terms=[])',
'V1NodeSelectorRequirement': '(key="", operator="")',
'V1PreferredSchedulingTerm': '(preference="", weight=1)',
'V1PodAffinityTerm': '(topology_key="")',
'V1WeightedPodAffinityTerm': '(pod_affinity_term="", weight=1)',
'V1OwnerReference': '(api_version="", kind="", name="", uid="")',
'V1HTTPHeader': '(name="", value="")',
'V1TCPSocketAction': '(port="")',
'V1VolumeDevice': '(device_path="", name="")',
'V1PodReadinessGate': '(condition_type="")',
'V1Sysctl': '(name="", value="")',
'V1ContainerStateTerminated': '(exit_code=0)',
'V1AzureFilePersistentVolumeSource': '(secret_name="",'
' share_name="")',
'V1CephFSPersistentVolumeSource': '(monitors=[])',
'V1CinderPersistentVolumeSource': '(volume_id="")',
'V1CSIPersistentVolumeSource': '(driver="", volume_handle="")',
'V1FlexPersistentVolumeSource': '(driver="")',
'V1GlusterfsPersistentVolumeSource': '(endpoints="", path="")',
'V1ISCSIPersistentVolumeSource': '(iqn="", lun=0,'
' target_portal="")',
'V1LocalVolumeSource': '(path="")',
'V1RBDPersistentVolumeSource': '(image="", monitors=[])',
'V1ScaleIOPersistentVolumeSource': '('
'gateway="",'
' secret_ref="",'
' system="")',
'V1DaemonSetStatus': '(current_number_scheduled=0, '
'desired_number_scheduled=0, '
'number_misscheduled=0, '
'number_ready=0)',
'V1DaemonSetCondition': '(status="", type="")',
'V1DeploymentCondition': '(status="", type="")',
'V1ReplicaSetStatus': '(replicas=0)',
'V1ReplicaSetCondition': '(status="", type="")',
'V1ResourceRule': '(verbs=[])',
'V1JobCondition': '(status="", type="")',
'V1IPBlock': '(cidr="")',
'V1EphemeralContainer': '(name="")',
'V1TopologySpreadConstraint': '(max_skew=0, topology_key="",'
' when_unsatisfiable="")',
'V1LimitRangeItem': '(type="")'
}
whole_kind = 'V1' + kind
if whole_kind in must_param.keys():
k8s_obj = eval('client.V1' + kind + must_param.get(whole_kind))
else:
k8s_obj = eval('client.V1' + kind + '()')
self._init_k8s_obj(k8s_obj, file_content_dict, must_param)
client_config = client.Configuration.get_default_copy()
client_config.client_side_validation = False
k8s_obj = eval(
'client.V1' + kind + '(local_vars_configuration=client_config)')
self._init_k8s_obj(k8s_obj, file_content_dict)
return k8s_obj
def _get_k8s_obj_from_file_content_dict(self, file_content_dict,
@ -499,7 +342,9 @@ class Transformer(object):
name = name.strip()
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
def _init_k8s_obj(self, obj, content, must_param):
def _init_k8s_obj(self, obj, content):
client_config = client.Configuration.get_default_copy()
client_config.client_side_validation = False
for key, value in content.items():
param_value = self._get_lower_case_name(key)
if hasattr(obj, param_value) and \
@ -511,12 +356,10 @@ class Transformer(object):
if obj_name == 'dict(str, str)':
setattr(obj, param_value, value)
else:
if obj_name in must_param.keys():
rely_obj = eval('client.' + obj_name +
must_param.get(obj_name))
else:
rely_obj = eval('client.' + obj_name + '()')
self._init_k8s_obj(rely_obj, value, must_param)
rely_obj = eval(
'client.' + obj_name +
'(local_vars_configuration=client_config)')
self._init_k8s_obj(rely_obj, value)
setattr(obj, param_value, rely_obj)
elif isinstance(value, list):
obj_name = obj.openapi_types.get(param_value)
@ -527,13 +370,10 @@ class Transformer(object):
rely_obj_name = \
re.findall(r".*\[([^\[\]]*)\].*", obj_name)[0]
for v in value:
if rely_obj_name in must_param.keys():
rely_obj = eval('client.' + rely_obj_name +
must_param.get(rely_obj_name))
else:
rely_obj = \
eval('client.' + rely_obj_name + '()')
self._init_k8s_obj(rely_obj, v, must_param)
rely_obj = \
eval('client.' + rely_obj_name +
'(local_vars_configuration=client_config)')
self._init_k8s_obj(rely_obj, v)
rely_objs.append(rely_obj)
setattr(obj, param_value, rely_objs)
@ -553,9 +393,8 @@ class Transformer(object):
return sorted_k8s_objs
def get_object_meta(self, content):
must_param = {}
v1_object_meta = client.V1ObjectMeta()
self._init_k8s_obj(v1_object_meta, content, must_param)
self._init_k8s_obj(v1_object_meta, content)
return v1_object_meta
# config_labels configures label