Skip to content

Commit

Permalink
Merge pull request #128 from 2gis/service-replace
Browse files Browse the repository at this point in the history
Use replace() for Services instead of patch()
  • Loading branch information
seleznev committed Oct 16, 2020
2 parents 88b3cb8 + 8572a43 commit 6d4895d
Show file tree
Hide file tree
Showing 5 changed files with 19 additions and 328 deletions.
18 changes: 11 additions & 7 deletions k8s_handle/k8s/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,24 +141,28 @@ def create(self):
raise ProvisioningError(e)
except ValueError as e:
log.error(e)
# WORKAROUND https://github.com/kubernetes-client/gen/issues/52, https://github.com/kubernetes-client/python/issues/1098
# WORKAROUND:
# - https://github.com/kubernetes-client/gen/issues/52
# - https://github.com/kubernetes-client/python/issues/1098
if self.kind not in ['custom_resource_definition', 'horizontal_pod_autoscaler']:
raise e

def replace(self, parameters):
try:
if self.kind in ['custom_resource_definition', 'pod_disruption_budget']:
self.body['metadata']['resourceVersion'] = parameters['resourceVersion']
if self.kind in ['service', 'custom_resource_definition', 'pod_disruption_budget']:
if 'resourceVersion' in parameters:
self.body['metadata']['resourceVersion'] = parameters['resourceVersion']

if self.kind in ['service']:
if 'clusterIP' not in self.body['spec'] and 'clusterIP' in parameters:
self.body['spec']['clusterIP'] = parameters['clusterIP']

if self.kind in ['custom_resource_definition']:
return self.api.replace_custom_resource_definition(
self.name, self.body,
)

if self.kind in ['service', 'service_account']:
if 'spec' in self.body:
self.body['spec']['ports'] = parameters.get('ports')

if self.kind in ['service_account']:
return getattr(self.api, 'patch_namespaced_{}'.format(self.kind))(
name=self.name, body=self.body, namespace=self.namespace
)
Expand Down
31 changes: 0 additions & 31 deletions k8s_handle/k8s/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,37 +267,6 @@ def replace_namespaced_persistent_volume_claim(self, name, body, namespace):
raise ApiException('Replace persistent volume claim fail')


class ServiceMetadata:
labels = {}

def __init__(self, annotations, labels):
if annotations is not None:
self.annotations = annotations
self.labels = labels


class ServiceSpec:
def __init__(self, case):
if case in ['case1', 'case2', 'case3']:
self.ports = [ServicePort(55)]
if case in ['case4', 'case5', 'case7', 'case8']:
self.ports = [ServicePort(55, 'test')]
if case in ['case6']:
self.ports = [ServicePort(55, 'test', 90)]


class ServicePort:
def __init__(self, port, name=None, target_port=None, node_port=None, protocol='TCP'):
self.port = port
self.name = name
self.node_port = node_port
self.protocol = protocol
if target_port is None:
self.target_port = port
else:
self.target_port = port


class CustomObjectsAPIMock:
pass

Expand Down
114 changes: 5 additions & 109 deletions k8s_handle/k8s/provisioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,50 +24,6 @@ def _replicas_count_are_equal(replicas):
replicas = [0 if r is None else r for r in replicas] # replace all None to 0
return all(r == replicas[0] for r in replicas)

@staticmethod
def _ports_are_equal(old_port, new_port):
for new_key in new_port.keys():
old_key = split_str_by_capital_letters(new_key)
if getattr(old_port, old_key, None) != new_port.get(new_key, None):
return False
return True

@staticmethod
def _get_missing_items_in_metadata_field(items, metadata, field):
if field not in metadata:
result = [item for item in items if 'kubernetes.io' not in item]
else:
result = [item for item in items if
item not in metadata[field] and 'kubernetes.io' not in item]

return result

@staticmethod
def _port_obj_to_str(port):
if hasattr(port, 'name') and hasattr(port, 'port'):
return '{} ({})'.format(port.name, port.port)
return '{}'.format(port.port)

def _notify_about_missing_items_in_template(self, items, missing_type):
skull = r"""
___
.-' `-.
/ \ / \\ Please pay attention to service {type}s!
. o\ /o . The next {type}(s) missing in template:
|___ ^ ___| "{list}"
|___| They won\'t be deleted after service apply.
|||||
"""

if len(items) != 0:
if missing_type in ['port', ]:
items = [self._port_obj_to_str(item) for item in items]

log_text = skull.format(type=missing_type, list=', '.join(items))
if settings.GET_ENVIRON_STRICT:
raise RuntimeError(log_text)
log.warning(log_text)

@staticmethod
def _is_job_complete(status):
if status.failed is not None:
Expand All @@ -80,60 +36,6 @@ def _is_job_complete(status):
else:
return False

def _get_missing_annotations_and_labels(self, old_metadata, new_metadata):
missing_annotations = []
missing_labels = []

if hasattr(old_metadata, 'annotations') and old_metadata.annotations is not None:
missing_annotations = self._get_missing_items_in_metadata_field(
old_metadata.annotations, new_metadata, 'annotations')
if hasattr(old_metadata, 'labels') and old_metadata.labels is not None:
missing_labels = self._get_missing_items_in_metadata_field(
old_metadata.labels, new_metadata, 'labels')

return missing_annotations, missing_labels

def _get_apply_ports(self, old_spec, new_spec):
ports = []

if hasattr(old_spec, 'ports') and old_spec.ports is not None:
if 'ports' not in new_spec:
return []

new_ports = new_spec['ports']
for old_port in old_spec.ports:
res = [item for item in new_ports if item['port'] == old_port.port]

if len(res) == 0:
log.warning('Port {} will be deleted'.format(old_port.port))
ports.append({'$patch': 'delete', 'port': old_port.port})

if len(res) == 1:
new_port = self._add_defaults_to_port(res[0])
if not self._ports_are_equal(old_port, new_port):
ports.append(new_port)

for new_port in new_ports:
res = [item for item in old_spec.ports if item.port == new_port['port']]

if len(res) == 0:
ports.append(new_port)

return ports

@staticmethod
def _add_defaults_to_port(port):
if 'name' not in port:
port['name'] = None
if 'nodePort' not in port:
port['node_port'] = None
if 'protocol' not in port:
port['protocol'] = 'TCP'
if 'targetPort' not in port:
port['target_port'] = port['port']

return port

def run(self, file_path):
if self.command == 'deploy':
self._deploy_all(file_path)
Expand Down Expand Up @@ -193,15 +95,8 @@ def _deploy(self, template_body, file_path):
parameters = {}

if template_body['kind'] == 'Service':
missing_annotations, missing_labels = \
self._get_missing_annotations_and_labels(resource.metadata, template_body['metadata'])
parameters['ports'] = self._get_apply_ports(resource.spec, template_body['spec'])

self._notify_about_missing_items_in_template(missing_annotations, 'annotation')
self._notify_about_missing_items_in_template(missing_labels, 'label')

if parameters['ports']:
log.info('Next ports will be applied: {}'.format(parameters['ports']))
if hasattr(resource.spec, 'cluster_ip'):
parameters['clusterIP'] = resource.spec.cluster_ip

if template_body['kind'] == 'PersistentVolumeClaim':
if self._is_pvc_specs_equals(resource.spec, template_body['spec']):
Expand All @@ -213,8 +108,9 @@ def _deploy(self, template_body, file_path):
log.warning('PersistentVolume has "{}" status, skip replacing'.format(resource.status.phase))
return

if template_body['kind'] in ['CustomResourceDefinition', 'PodDisruptionBudget']:
parameters['resourceVersion'] = resource.metadata.resource_version
if template_body['kind'] in ['Service', 'CustomResourceDefinition', 'PodDisruptionBudget']:
if hasattr(resource.metadata, 'resource_version'):
parameters['resourceVersion'] = resource.metadata.resource_version

kube_client.replace(parameters)

Expand Down
6 changes: 3 additions & 3 deletions k8s_handle/k8s/test_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ def test_app_replace(self):
self.assertEqual(res, {'key1': 'value1'})

def test_app_replace_service(self):
deployment = AdapterBuiltinKind(
service = AdapterBuiltinKind(
api=K8sClientMock(''),
spec={'kind': 'Service', 'metadata': {'name': ''}, 'spec': {'replicas': 1}})
res = deployment.replace({})
spec={'kind': 'Service', 'metadata': {'name': ''}, 'spec': {'type': 'ClusterIP'}})
res = service.replace({})
self.assertEqual(res, {'key1': 'value1'})

def test_app_delete_fail(self):
Expand Down
Loading

0 comments on commit 6d4895d

Please sign in to comment.