From 5a99afb52abd1e8edeb81db95237703c9159d0aa Mon Sep 17 00:00:00 2001
From: jianxiaoguo <842666271@qq.com>
Date: Wed, 3 Aug 2022 10:50:20 +0800
Subject: [PATCH] chore(controller): add volume expand support use patch

---
 .../templates/controller-ingress.yaml         |  2 +-
 rootfs/api/models/volume.py                   |  2 +-
 rootfs/api/tests/test_volume.py               |  6 +-
 rootfs/api/urls.py                            |  2 +-
 rootfs/scheduler/mock.py                      | 75 +++++++++++++++++++
 rootfs/scheduler/resources/pvc.py             |  6 +-
 6 files changed, 84 insertions(+), 9 deletions(-)

diff --git a/charts/controller/templates/controller-ingress.yaml b/charts/controller/templates/controller-ingress.yaml
index 1df74d715..13a97c2b9 100644
--- a/charts/controller/templates/controller-ingress.yaml
+++ b/charts/controller/templates/controller-ingress.yaml
@@ -25,7 +25,7 @@ spec:
         {{- end }}
         backend:
           service:
-            name: drycc-controller
+            name: drycc-controller-api
             port:
               number: 80
   {{- if .Values.global.certManagerEnabled }}
diff --git a/rootfs/api/models/volume.py b/rootfs/api/models/volume.py
index 71c331db1..b5ac36d23 100644
--- a/rootfs/api/models/volume.py
+++ b/rootfs/api/models/volume.py
@@ -46,7 +46,7 @@ def expand(self, size):
                 "size": self._get_size(self.size),
                 "storage_class": settings.DRYCC_APP_STORAGE_CLASS,
             }
-            self._scheduler.pvc.put(self.app.id, self.name, **kwargs)
+            self._scheduler.pvc.patch(self.app.id, self.name, **kwargs)
         except KubeException as e:
             msg = 'There was a problem expand the volume ' \
                     '{} for {}'.format(self.name, self.app_id)
diff --git a/rootfs/api/tests/test_volume.py b/rootfs/api/tests/test_volume.py
index 408642751..fbf935cd0 100644
--- a/rootfs/api/tests/test_volume.py
+++ b/rootfs/api/tests/test_volume.py
@@ -86,13 +86,13 @@ def test_volume_expand(self, mock_requests):
         data = {'name': 'myvolume1', 'size': '500G'}
         self.client.post('/v2/apps/{}/volumes'.format(app_id), data=data)
 
-        # Put
+        # Patch
         url = '/v2/apps/{app_id}/volumes/{volume}'.format(app_id=app_id,
                                                           volume='myvolume1')
-        response = self.client.put(url, {'name': 'myvolume1', 'size': '100G'})
+        response = self.client.patch(url, {'name': 'myvolume1', 'size': '100G'})
         self.assertEqual(response.status_code, 400)
 
-        response = self.client.put(url, {'name': 'myvolume1', 'size': '1024G'})
+        response = self.client.patch(url, {'name': 'myvolume1', 'size': '1024G'})
         self.assertEqual(response.status_code, 200)
         # Fetch
         url = '/v2/apps/{app_id}/volumes'.format(app_id=app_id)
diff --git a/rootfs/api/urls.py b/rootfs/api/urls.py
index eed7c7f47..0230e9b32 100644
--- a/rootfs/api/urls.py
+++ b/rootfs/api/urls.py
@@ -79,7 +79,7 @@
     url(r"^apps/(?P<id>{})/volumes/?$".format(settings.APP_URL_REGEX),
         views.AppVolumesViewSet.as_view({'get': 'list', 'post': 'create'})),
     url(r"^apps/(?P<id>{})/volumes/(?P<name>[-_\w]+)/?$".format(settings.APP_URL_REGEX),
-        views.AppVolumesViewSet.as_view({'put': 'expand', 'delete': 'destroy'})),
+        views.AppVolumesViewSet.as_view({'patch': 'expand', 'delete': 'destroy'})),
     url(r"^apps/(?P<id>{})/volumes/(?P<name>[-_\w]+)/path/?$".format(settings.APP_URL_REGEX),
         views.AppVolumeMountPathViewSet.as_view({'patch': 'path'})),
     # application resources
diff --git a/rootfs/scheduler/mock.py b/rootfs/scheduler/mock.py
index 458548351..34b6f1288 100644
--- a/rootfs/scheduler/mock.py
+++ b/rootfs/scheduler/mock.py
@@ -799,6 +799,79 @@ def put(request, context):
     return request.json()
 
 
+def patch(request, context):
+    """Process a PATCH request to the kubernetes API"""
+    url = cache_key(request.url)
+    # type is the second last element
+    resource_type = get_type(request.url)
+    # check if the namespace being posted to exists
+    if resource_type != 'namespaces':
+        namespace = get_namespace(url, resource_type)
+        if cache.get(namespace) is None:
+            context.status_code = 404
+            context.reason = 'Not Found'
+            return {}
+
+    # figure out main resource if in subresource
+    original_url = url
+    subresource, resource_type, url = is_subresource(resource_type, url)
+    if subresource != resource_type:
+        cache.set(original_url, request.json(), None)
+
+    item = cache.get(url)
+    if item is None:
+        context.status_code = 404
+        context.reason = 'Not Found'
+        return {}
+    # raise a 503 when we want to intentionally test for it
+    elif item['metadata']['name'] == 'image-pull-failed-test':
+        context.status_code = 503
+        context.reason = 'Network Unreachable'
+        return {}
+
+    data = request.json()
+
+    # merge new data into old but keep labels separate in case they changed
+    if 'labels' in data['metadata']:
+        labels = data['metadata'].pop('labels')
+        item['metadata'].update(data['metadata'])
+        data['metadata'] = item['metadata']
+        # make sure only new labels are used
+        data['metadata']['labels'] = labels
+
+    # split out deployments and replicasets? due to upsert_pods
+    if resource_type in ['replicationcontrollers', 'replicasets', 'deployments']:
+        if subresource == 'scale':
+            # has minimal info so need to copy data
+            replicas = data['spec']['replicas']
+            data = copy.deepcopy(item)  # full copy
+            data['spec']['replicas'] = replicas
+
+        if 'status' not in data:
+            # just use what was set last time
+            data['status'] = {'observedGeneration': item['status']['observedGeneration']}
+
+        data['metadata']['resourceVersion'] += 1
+        data['metadata']['generation'] += 1
+        data['status']['observedGeneration'] += 1
+
+        # Update the individual resource
+        cache.set(url, data, None)
+
+        if resource_type in ['replicationcontrollers', 'replicasets']:
+            upsert_pods(data, url)
+        elif resource_type == 'deployments':
+            manage_replicasets(data, url)
+    else:
+        # Update the individual resource
+        cache.set(url, data, None)
+
+    context.status_code = 200
+    context.reason = 'OK'
+
+    return request.json()
+
+
 def delete(request, context):
     """Process a DELETE request to the kubernetes API"""
     url = cache_key(request.url)
@@ -892,6 +965,8 @@ def mock_kubernetes(request, context):
         response = get(request, context)
     elif request.method == 'PUT':
         response = put(request, context)
+    elif request.method == 'PATCH':
+        response = patch(request, context)
     elif request.method == 'DELETE':
         response = delete(request, context)
 
diff --git a/rootfs/scheduler/resources/pvc.py b/rootfs/scheduler/resources/pvc.py
index 02789ac7f..ef0210ee3 100644
--- a/rootfs/scheduler/resources/pvc.py
+++ b/rootfs/scheduler/resources/pvc.py
@@ -65,14 +65,14 @@ def create(self, namespace, name, **kwargs):
                 "create persistentvolumeclaim {}".format(namespace))
         return response
 
-    def put(self, namespace, name, **kwargs):
+    def patch(self, namespace, name, **kwargs):
         url = self.api('/namespaces/{}/persistentvolumeclaims/{}', namespace,
                        name)
         data = self.manifest(namespace, name, **kwargs)
-        response = self.http_put(url, json=data)
+        response = self.http_patch(url, json=data, headers={"Content-Type": "application/merge-patch+json"})  # noqa
         if self.unhealthy(response.status_code):
             self.log(namespace, 'template used: {}'.format(json.dumps(data, indent=4)), 'DEBUG')  # noqa
-            raise KubeHTTPException(response, 'update HorizontalPodAutoscaler "{}"', name)
+            raise KubeHTTPException(response, 'update persistentvolumeclaims "{}"', name)
         return response
 
     def delete(self, namespace, name):
