def _patch(self, _) -> None: """Patch the Kubernetes service created by Juju to map the correct port. Raises: PatchFailed: if patching fails due to lack of permissions, or otherwise. """ if not self.charm.unit.is_leader(): return client = Client() try: client.patch(Service, self._app, self.service, patch_type=PatchType.MERGE) except ApiError as e: if e.status.code == 403: logger.error( "Kubernetes service patch failed: `juju trust` this application." ) else: logger.error("Kubernetes service patch failed: %s", str(e)) else: logger.info("Kubernetes service '%s' patched successfully", self._app)
async def test_seldon_deployment(ops_test: OpsTest): namespace = ops_test.model_name client = Client() this_ns = client.get(res=Namespace, name=namespace) this_ns.metadata.labels.update({"serving.kubeflow.org/inferenceservice": "enabled"}) client.patch(res=Namespace, name=this_ns.metadata.name, obj=this_ns) SeldonDeployment = create_namespaced_resource( group="machinelearning.seldon.io", version="v1", kind="seldondeployment", plural="seldondeployments", verbs=None, ) with open("examples/serve-simple-v1.yaml") as f: sdep = SeldonDeployment(yaml.safe_load(f.read())) client.create(sdep, namespace=namespace) for i in range(30): dep = client.get(SeldonDeployment, "seldon-model", namespace=namespace) state = dep.get("status", {}).get("state") if state == "Available": logger.info(f"SeldonDeployment status == {state}") break else: logger.info(f"SeldonDeployment status == {state} (waiting for 'Available')") time.sleep(5) else: pytest.fail("Waited too long for seldondeployment/seldon-model!") service_name = "seldon-model-example-classifier" service = client.get(Service, name=service_name, namespace=namespace) service_ip = service.spec.clusterIP service_port = next(p for p in service.spec.ports if p.name == "http").port response = requests.post( f"http://{service_ip}:{service_port}/predict", json={ "data": { "names": ["a", "b"], "tensor": {"shape": [2, 2], "values": [0, 0, 1, 1]}, } }, ) response.raise_for_status() response = response.json() assert response["data"]["names"] == ["proba"] assert response["data"]["tensor"]["shape"] == [2, 1] assert response["meta"] == {}
def test_patch_global(client: lightkube.Client): req = respx.patch("https://localhost:9443/api/v1/nodes/xx").respond(json={'metadata': {'name': 'xx'}}) node = client.patch(Node, "xx", [{"op": "add", "path": "/metadata/labels/x", "value": "y"}], patch_type=types.PatchType.JSON) assert node.metadata.name == 'xx' assert req.calls[0][0].headers['Content-Type'] == "application/json-patch+json" # PatchType.APPLY + force req = respx.patch("https://localhost:9443/api/v1/nodes/xy?fieldManager=test&force=true").respond( json={'metadata': {'name': 'xy'}}) node = client.patch(Node, "xy", Pod(metadata=ObjectMeta(labels={'l': 'ok'})), patch_type=types.PatchType.APPLY, field_manager='test', force=True) assert node.metadata.name == 'xy' assert req.calls[0][0].headers['Content-Type'] == "application/apply-patch+yaml"
def set_manifest(manifest): client = Client() errors = [] for resource in manifest: try: client.create(resource) except ApiError as err: if err.status.reason == "AlreadyExists": client.patch(type(resource), resource.metadata.name, resource) else: errors.append(err) return errors
def test_namespaced_methods(obj_name): client = Client() config = ConfigMap( metadata=ObjectMeta(name=obj_name, namespace='default'), data={'key1': 'value1', 'key2': 'value2'} ) # create config = client.create(config) try: assert config.metadata.name == obj_name assert config.data['key1'] == 'value1' assert config.data['key2'] == 'value2' # replace config.data['key1'] = 'new value' config = client.replace(config) assert config.data['key1'] == 'new value' assert config.data['key2'] == 'value2' # patch with PatchType.STRATEGIC patch = {'metadata': {'labels': {'app': 'xyz'}}} config = client.patch(ConfigMap, name=obj_name, obj=patch) assert config.metadata.labels['app'] == 'xyz' # get config2 = client.get(ConfigMap, name=obj_name) assert config.metadata.creationTimestamp == config2.metadata.creationTimestamp # list configs = [config.metadata.name for config in client.list(ConfigMap)] assert obj_name in configs finally: client.delete(ConfigMap, name=obj_name)
def test_patching(obj_name): client = Client() service = Service( metadata=ObjectMeta(name=obj_name), spec=ServiceSpec( ports=[ServicePort(name='a', port=80, targetPort=8080)], selector={'app': 'not-existing'} ) ) # create client.create(service) try: # patch with PatchType.STRATEGIC patch = {'spec': {'ports': [{'name': 'b', 'port':81, 'targetPort': 8081}]}} service = client.patch(Service, name=obj_name, obj=patch) assert len(service.spec.ports) == 2 assert {port.name for port in service.spec.ports} == {'a', 'b'} # strategic - patch merge key: port # we also try to send a Resource type for patching patch = Service(spec=ServiceSpec(ports=[ServicePort(name='b', port=81, targetPort=8082)])) service = client.patch(Service, name=obj_name, obj=patch) assert len(service.spec.ports) == 2 for port in service.spec.ports: if port.port == 81: assert port.targetPort == 8082 # patch with PatchType.MERGE # merge will replace the full list patch = {'spec': {'ports': [{'name': 'b', 'port': 81, 'targetPort': 8081}]}} service = client.patch(Service, name=obj_name, obj=patch, patch_type=PatchType.MERGE) assert len(service.spec.ports) == 1 assert service.spec.ports[0].port == 81 # patch with PatchType.JSON patch = [ {'op': 'add', 'path': '/spec/ports/-', 'value': {'name': 'a', 'port': 80, 'targetPort': 8080}} ] service = client.patch(Service, name=obj_name, obj=patch, patch_type=PatchType.JSON) assert len(service.spec.ports) == 2 assert service.spec.ports[1].port == 80 finally: client.delete(Service, name=obj_name)
def test_patch_namespaced(client: lightkube.Client): # Default PatchType.STRATEGIC req = respx.patch("https://localhost:9443/api/v1/namespaces/default/pods/xx").respond(json={'metadata': {'name': 'xx'}}) pod = client.patch(Pod, "xx", Pod(metadata=ObjectMeta(labels={'l': 'ok'}))) assert pod.metadata.name == 'xx' assert req.calls[0][0].headers['Content-Type'] == "application/strategic-merge-patch+json" # PatchType.MERGE req = respx.patch("https://localhost:9443/api/v1/namespaces/other/pods/xx").respond(json={'metadata': {'name': 'xx'}}) pod = client.patch(Pod, "xx", Pod(metadata=ObjectMeta(labels={'l': 'ok'})), namespace='other', patch_type=types.PatchType.MERGE, force=True) assert pod.metadata.name == 'xx' assert req.calls[0][0].headers['Content-Type'] == "application/merge-patch+json" assert 'force' not in str(req.calls[0][0].url) # force is ignored for non APPLY patch types # PatchType.APPLY req = respx.patch("https://localhost:9443/api/v1/namespaces/other/pods/xy?fieldManager=test").respond( json={'metadata': {'name': 'xy'}}) pod = client.patch(Pod, "xy", Pod(metadata=ObjectMeta(labels={'l': 'ok'})), namespace='other', patch_type=types.PatchType.APPLY, field_manager='test') assert pod.metadata.name == 'xy' assert req.calls[0][0].headers['Content-Type'] == "application/apply-patch+yaml" # PatchType.APPLY + force req = respx.patch("https://localhost:9443/api/v1/namespaces/other/pods/xz?fieldManager=test&force=true").respond( json={'metadata': {'name': 'xz'}}) pod = client.patch(Pod, "xz", Pod(metadata=ObjectMeta(labels={'l': 'ok'})), namespace='other', patch_type=types.PatchType.APPLY, field_manager='test', force=True) assert pod.metadata.name == 'xz' assert req.calls[0][0].headers['Content-Type'] == "application/apply-patch+yaml" # PatchType.APPLY without field_manager with pytest.raises(ValueError, match="field_manager"): client.patch(Pod, "xz", Pod(metadata=ObjectMeta(labels={'l': 'ok'})), namespace='other', patch_type=types.PatchType.APPLY)
def test_apply(obj_name): client = Client(field_manager='lightkube') config = ConfigMap( apiVersion='v1', # apiVersion and kind are required for server-side apply kind='ConfigMap', metadata=ObjectMeta(name=obj_name, namespace='default'), data={'key1': 'value1', 'key2': 'value2'} ) # create with apply c = client.apply(config) try: assert c.metadata.name == obj_name assert c.data['key1'] == 'value1' assert c.data['key2'] == 'value2' # modify config.data['key2'] = 'new value' del config.data['key1'] config.data['key3'] = 'value3' c = client.apply(config) assert c.data['key2'] == 'new value' assert c.data['key3'] == 'value3' assert 'key1' not in c.data # remove all keys config.data.clear() c = client.apply(config) assert not c.data # use the patch equivalent config.data['key1'] = 'new value' c = client.patch(ConfigMap, obj_name, config.to_dict(), patch_type=PatchType.APPLY) assert c.data['key1'] == 'new value' finally: client.delete(ConfigMap, name=obj_name)