From 3a8dd2aba9ddb193312187040ae06d110da90747 Mon Sep 17 00:00:00 2001 From: marcin Date: Sat, 6 Jun 2020 14:31:08 +0200 Subject: [PATCH 01/47] add fission class --- sebs/fission/fission.py | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 sebs/fission/fission.py diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py new file mode 100644 index 00000000..033bc453 --- /dev/null +++ b/sebs/fission/fission.py @@ -0,0 +1,45 @@ +import base64 +import datetime +import json +import logging +import os +import shutil +import time +import subprocess +import uuid +from typing import Dict, List, Optional, Tuple, Union, cast + +from sebs import utils +from sebs.benchmark import Benchmark +from sebs.cache import Cache +from sebs.config import SeBSConfig +from ..faas.function import Function +from ..faas.storage import PersistentStorage +from ..faas.system import System + + + +class fission(System): + +def __init__( + self, + sebs_config: SeBSConfig, + cache_client: Cache, + docker_client: docker.client, + ): + super().__init__(sebs_config, cache_client, docker_client) + +def initialize(self, config: Dict[str, str] = {}): + pass + +def get_storage(self, replace_existing: bool) -> PersistentStorage: + pass + +def package_code(self, benchmark: sebs.benchmark.Benchmark) -> Tuple[str, int]: + pass + +def get_function(self, code_package: sebs.benchmark.Benchmark) -> Function: + pass + +def name() -> str: + pass \ No newline at end of file From 78605d10ec838d9055e0b9cae9da9ce37a9e76df Mon Sep 17 00:00:00 2001 From: Marcin Date: Sat, 6 Jun 2020 18:59:34 +0200 Subject: [PATCH 02/47] add run fission script --- sebs/fission/fission.py | 4 ++-- sebs/fission/run_fission.sh | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100755 sebs/fission/run_fission.sh diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 033bc453..9ce92813 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -30,7 +30,7 @@ def __init__( super().__init__(sebs_config, cache_client, docker_client) def initialize(self, config: Dict[str, str] = {}): - pass + subprocess.call(['./run_fission.sh']) def get_storage(self, replace_existing: bool) -> PersistentStorage: pass @@ -42,4 +42,4 @@ def get_function(self, code_package: sebs.benchmark.Benchmark) -> Function: pass def name() -> str: - pass \ No newline at end of file + pass diff --git a/sebs/fission/run_fission.sh b/sebs/fission/run_fission.sh new file mode 100755 index 00000000..a98c6746 --- /dev/null +++ b/sebs/fission/run_fission.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +if ! minikube version +then + echo "ERROR: \"minikube\" required." + exit 1 +fi +minikube start --vm-driver=docker +if ! kubectl version +then + echo "ERROR: \"kubectl\" required." + exit 1 +fi +if ! helm version +then + echo "ERROR: \"helm\" required." + exit 1 +fi +export FISSION_NAMESPACE=fission-local-suu +if ! kubectl get namespace | grep $FISSION_NAMESPACE +then + echo "No proper fission namespace... Installing Fission as ${FISSION_NAMESPACE}..." + kubectl create namespace $FISSION_NAMESPACE + helm install --namespace $FISSION_NAMESPACE --name-template fission https://github.com/fission/fission/releases/download/1.9.0/fission-all-1.9.0.tgz +fi +if ! fission > /dev/null 2>&1 +then + echo "No fission CLI - installing..." + case $(uname | tr '[:upper:]' '[:lower:]') in + linux*) + export FISSION_OS_NAME=linux + ;; + darwin*) + export FISSION_OS_NAME=osx + ;; + esac + curl -Lo fission https://github.com/fission/fission/releases/download/1.9.0/fission-cli-${FISSION_OS_NAME} && chmod +x fission && sudo mv fission /usr/local/bin/ +fi +echo "Fission is ready to use" From c8c10126c3527a3f7462f420b600ad5f5a7c8aa4 Mon Sep 17 00:00:00 2001 From: Marcin Date: Sat, 6 Jun 2020 20:07:57 +0200 Subject: [PATCH 03/47] add init and lint --- sebs/fission/__init__.py | 1 + sebs/fission/fission.py | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 sebs/fission/__init__.py diff --git a/sebs/fission/__init__.py b/sebs/fission/__init__.py new file mode 100644 index 00000000..0d7eef58 --- /dev/null +++ b/sebs/fission/__init__.py @@ -0,0 +1 @@ +from .fission import Fission diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 9ce92813..93977e6c 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -6,6 +6,7 @@ import shutil import time import subprocess +import docker import uuid from typing import Dict, List, Optional, Tuple, Union, cast @@ -18,10 +19,8 @@ from ..faas.system import System - -class fission(System): - -def __init__( +class Fission(System): + def __init__( self, sebs_config: SeBSConfig, cache_client: Cache, @@ -29,17 +28,22 @@ def __init__( ): super().__init__(sebs_config, cache_client, docker_client) + def initialize(self, config: Dict[str, str] = {}): - subprocess.call(['./run_fission.sh']) + subprocess.call(["./run_fission.sh"]) + def get_storage(self, replace_existing: bool) -> PersistentStorage: - pass + pass + def package_code(self, benchmark: sebs.benchmark.Benchmark) -> Tuple[str, int]: - pass + pass + def get_function(self, code_package: sebs.benchmark.Benchmark) -> Function: - pass + pass + def name() -> str: - pass + pass From 5443c4ad039fd4d76bd13c0c522e22a7c52c4f9d Mon Sep 17 00:00:00 2001 From: Marcin Date: Sat, 6 Jun 2020 21:56:59 +0200 Subject: [PATCH 04/47] wip --- sebs/fission/fission.py | 114 +++++++++++++++++++++--- sebs/fission/fissionFunction.py | 33 +++++++ sebs/fission/update_fission_function.sh | 2 + 3 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 sebs/fission/fissionFunction.py create mode 100755 sebs/fission/update_fission_function.sh diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 93977e6c..1a99e601 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -14,9 +14,11 @@ from sebs.benchmark import Benchmark from sebs.cache import Cache from sebs.config import SeBSConfig -from ..faas.function import Function -from ..faas.storage import PersistentStorage -from ..faas.system import System +from sebs.faas.function import Function +from sebs.faas.storage import PersistentStorage +from sebs.faas.system import System +from sebs.fission.fissionFunction import FissionFunction +from sebs.benchmark import Benchmark class Fission(System): @@ -28,22 +30,108 @@ def __init__( ): super().__init__(sebs_config, cache_client, docker_client) + def initialize(self, config: Dict[str, str] = {}): + subprocess.call(["./run_fission.sh"]) + + def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: + CONFIG_FILES = { + "python": ["handler.py", "requirements.txt", ".python_packages"], + "nodejs": ["handler.js", "package.json", "node_modules"], + } + package_config = CONFIG_FILES[benchmark.language] + function_dir = os.path.join(dir, "function") + os.makedirs(function_dir) + for file in os.listdir(dir): + if file not in package_config: + file = os.path.join(dir, file) + shutil.move(file, function_dir) + bytes_size = os.path.getsize(function_dir) + return function_dir, bytes_size + + def update_function(self, name: str, path: str): + subprocess.call(["./update_fission_fuction.sh", name, path]) + + def get_function(self, code_package: Benchmark) -> Function: + + path, size = self.package_code(code_package) -def initialize(self, config: Dict[str, str] = {}): - subprocess.call(["./run_fission.sh"]) + if ( + code_package.language_version + not in self.system_config.supported_language_versions( + self.name(), code_package.language_name + ) + ): + raise Exception( + "Unsupported {language} version {version} in Fission!".format( + language=code_package.language_name, + version=code_package.language_version, + ) + ) + benchmark = code_package.benchmark + if code_package.is_cached and code_package.is_cached_valid: + func_name = code_package.cached_config["name"] + code_location = code_package.code_location + logging.info( + "Using cached function {fname} in {loc}".format( + fname=func_name, loc=code_location + ) + ) + return FissionFunction(self) + elif code_package.is_cached: + func_name = code_package.cached_config["name"] + code_location = code_package.code_location + timeout = code_package.benchmark_config.timeout + memory = code_package.benchmark_config.memory + language = code_package.language -def get_storage(self, replace_existing: bool) -> PersistentStorage: - pass + self.update_function(func_name, path) + cached_cfg = code_package.cached_config + cached_cfg["code_size"] = size + cached_cfg["timeout"] = timeout + cached_cfg["memory"] = memory + cached_cfg["hash"] = code_package.hash + self.cache_client.update_function( + self.name(), benchmark, code_package.language_name, path, cached_cfg + ) + # FIXME: fix after dissociating code package and benchmark + code_package.query_cache() -def package_code(self, benchmark: sebs.benchmark.Benchmark) -> Tuple[str, int]: - pass + logging.info( + "Updating cached function {fname} in {loc}".format( + fname=func_name, loc=code_location + ) + ) + return FissionFunction(self) + # no cached instance, create package and upload code + else: -def get_function(self, code_package: sebs.benchmark.Benchmark) -> Function: - pass + code_location = code_package.code_location + language = code_package.language_name + language_runtime = code_package.language_version + timeout = code_package.benchmark_config.timeout + memory = code_package.benchmark_config.memory + func_name = "{}-{}-{}".format(benchmark, language, memory) -def name() -> str: - pass + self.cache_client.add_function( + deployment=self.name(), + benchmark=benchmark, + language=language, + code_package=path, + language_config={ + "name": func_name, + "code_size": size, + "runtime": language_runtime, + "role": "FissionRole", + "memory": memory, + "timeout": timeout, + "hash": code_package.hash, + "url": "WtfUrl" + } + ) + # FIXME: fix after dissociating code package and benchmark + code_package.query_cache() + return FissionFunction(self) diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py new file mode 100644 index 00000000..eb04c6cb --- /dev/null +++ b/sebs/fission/fissionFunction.py @@ -0,0 +1,33 @@ +from abc import ABC +from enum import Enum +from sebs.faas.function import Function +from typing import List + + +class Trigger(ABC): + class TriggerType(Enum): + HTTP = 0 + STORAGE = 1 + + def sync_invoke(self): + pass + + def async_invoke(self): + pass + + +class FissionFunction(Function): + _triggers: List[Trigger] + + def __init__(self, name: str): + self._name = name + + @property + def name(self): + return self._name + + def sync_invoke(self, payload: dict): + raise Exception("Non-trigger invoke not supported!") + + def async_invoke(self, payload: dict): + raise Exception("Non-trigger invoke not supported!") diff --git a/sebs/fission/update_fission_function.sh b/sebs/fission/update_fission_function.sh new file mode 100755 index 00000000..192f3c38 --- /dev/null +++ b/sebs/fission/update_fission_function.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +fission fn update --name $1 --code $2 From 25f3f0e27455c9ee416e82208fbedc7e1a49258f Mon Sep 17 00:00:00 2001 From: Marcin Date: Sat, 6 Jun 2020 22:08:02 +0200 Subject: [PATCH 05/47] add create function --- sebs/fission/create_fission_function.sh | 3 +++ sebs/fission/fission.py | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100755 sebs/fission/create_fission_function.sh diff --git a/sebs/fission/create_fission_function.sh b/sebs/fission/create_fission_function.sh new file mode 100755 index 00000000..5c7c6086 --- /dev/null +++ b/sebs/fission/create_fission_function.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +fission env create --name $4 --image $2 +fission function create --name $1 --env $4 --code $3 diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 1a99e601..a075b6ab 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -51,6 +51,15 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: def update_function(self, name: str, path: str): subprocess.call(["./update_fission_fuction.sh", name, path]) + def create_function(self, name: str, language: str, path: str): + CONFIG_FILES = { + "python": "fission/python-env", + "nodejs": "fission/node-env" + } + + subprocess.call(["./create_fission_function.sh", + name, CONFIG_FILES[language], path, language]) + def get_function(self, code_package: Benchmark) -> Function: path, size = self.package_code(code_package) @@ -116,6 +125,8 @@ def get_function(self, code_package: Benchmark) -> Function: func_name = "{}-{}-{}".format(benchmark, language, memory) + self.create_function(func_name, language, path) + self.cache_client.add_function( deployment=self.name(), benchmark=benchmark, From fd24ea31fe4061814c23e7fbc67aa1f71eeff959 Mon Sep 17 00:00:00 2001 From: Marcin Date: Sat, 6 Jun 2020 22:52:02 +0200 Subject: [PATCH 06/47] Add making request --- sebs/fission/fissionFunction.py | 17 ++--------------- sebs/fission/run_fission_function.sh | 3 +++ 2 files changed, 5 insertions(+), 15 deletions(-) create mode 100755 sebs/fission/run_fission_function.sh diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index eb04c6cb..116212fc 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -2,23 +2,10 @@ from enum import Enum from sebs.faas.function import Function from typing import List - - -class Trigger(ABC): - class TriggerType(Enum): - HTTP = 0 - STORAGE = 1 - - def sync_invoke(self): - pass - - def async_invoke(self): - pass +import subprocess class FissionFunction(Function): - _triggers: List[Trigger] - def __init__(self, name: str): self._name = name @@ -27,7 +14,7 @@ def name(self): return self._name def sync_invoke(self, payload: dict): - raise Exception("Non-trigger invoke not supported!") + subprocess.call(["./run_fission_function.sh", self.name]) def async_invoke(self, payload: dict): raise Exception("Non-trigger invoke not supported!") diff --git a/sebs/fission/run_fission_function.sh b/sebs/fission/run_fission_function.sh new file mode 100755 index 00000000..8f97e5e3 --- /dev/null +++ b/sebs/fission/run_fission_function.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +fission function test --name $1 From d4f2687648562a2179847aa853b0850393fd6e61 Mon Sep 17 00:00:00 2001 From: Marcin Date: Sat, 6 Jun 2020 23:31:14 +0200 Subject: [PATCH 07/47] linting --- sebs/fission/__init__.py | 2 +- sebs/fission/fission.py | 68 ++++++++----------- .../create_fission_function.sh | 0 .../{ => fissionBashScripts}/run_fission.sh | 0 .../run_fission_function.sh | 0 .../update_fission_function.sh | 0 sebs/fission/fissionFunction.py | 3 - 7 files changed, 29 insertions(+), 44 deletions(-) rename sebs/fission/{ => fissionBashScripts}/create_fission_function.sh (100%) rename sebs/fission/{ => fissionBashScripts}/run_fission.sh (100%) rename sebs/fission/{ => fissionBashScripts}/run_fission_function.sh (100%) rename sebs/fission/{ => fissionBashScripts}/update_fission_function.sh (100%) diff --git a/sebs/fission/__init__.py b/sebs/fission/__init__.py index 0d7eef58..1a03e037 100644 --- a/sebs/fission/__init__.py +++ b/sebs/fission/__init__.py @@ -1 +1 @@ -from .fission import Fission +from .fission import Fission # noqa diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index a075b6ab..7d06b3ae 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -1,21 +1,12 @@ -import base64 -import datetime -import json import logging import os import shutil -import time import subprocess import docker -import uuid -from typing import Dict, List, Optional, Tuple, Union, cast - -from sebs import utils -from sebs.benchmark import Benchmark +from typing import Dict, Tuple from sebs.cache import Cache from sebs.config import SeBSConfig from sebs.faas.function import Function -from sebs.faas.storage import PersistentStorage from sebs.faas.system import System from sebs.fission.fissionFunction import FissionFunction from sebs.benchmark import Benchmark @@ -23,45 +14,46 @@ class Fission(System): def __init__( - self, - sebs_config: SeBSConfig, - cache_client: Cache, - docker_client: docker.client, + self, sebs_config: SeBSConfig, cache_client: Cache, docker_client: docker.client ): super().__init__(sebs_config, cache_client, docker_client) def initialize(self, config: Dict[str, str] = {}): - subprocess.call(["./run_fission.sh"]) + subprocess.call(["./fissionBashScripts/run_fission.sh"]) def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: CONFIG_FILES = { "python": ["handler.py", "requirements.txt", ".python_packages"], "nodejs": ["handler.js", "package.json", "node_modules"], } - package_config = CONFIG_FILES[benchmark.language] - function_dir = os.path.join(dir, "function") + directory = benchmark.code_location + package_config = CONFIG_FILES[benchmark.language_name] + function_dir = os.path.join(directory, "function") os.makedirs(function_dir) - for file in os.listdir(dir): + for file in os.listdir(directory): if file not in package_config: - file = os.path.join(dir, file) + file = os.path.join(directory, file) shutil.move(file, function_dir) bytes_size = os.path.getsize(function_dir) return function_dir, bytes_size def update_function(self, name: str, path: str): - subprocess.call(["./update_fission_fuction.sh", name, path]) + subprocess.call(["./fissionBashScripts/update_fission_fuction.sh", name, path]) def create_function(self, name: str, language: str, path: str): - CONFIG_FILES = { - "python": "fission/python-env", - "nodejs": "fission/node-env" - } - - subprocess.call(["./create_fission_function.sh", - name, CONFIG_FILES[language], path, language]) + CONFIG_FILES = {"python": "fission/python-env", "nodejs": "fission/node-env"} + + subprocess.call( + [ + "./fissionBashScripts/create_fission_function.sh", + name, + CONFIG_FILES[language], + path, + language, + ] + ) def get_function(self, code_package: Benchmark) -> Function: - path, size = self.package_code(code_package) if ( @@ -86,13 +78,12 @@ def get_function(self, code_package: Benchmark) -> Function: fname=func_name, loc=code_location ) ) - return FissionFunction(self) + return FissionFunction(func_name) elif code_package.is_cached: func_name = code_package.cached_config["name"] code_location = code_package.code_location timeout = code_package.benchmark_config.timeout memory = code_package.benchmark_config.memory - language = code_package.language self.update_function(func_name, path) @@ -104,19 +95,14 @@ def get_function(self, code_package: Benchmark) -> Function: self.cache_client.update_function( self.name(), benchmark, code_package.language_name, path, cached_cfg ) - # FIXME: fix after dissociating code package and benchmark code_package.query_cache() - logging.info( "Updating cached function {fname} in {loc}".format( fname=func_name, loc=code_location ) ) - - return FissionFunction(self) - # no cached instance, create package and upload code + return FissionFunction(func_name) else: - code_location = code_package.code_location language = code_package.language_name language_runtime = code_package.language_version @@ -140,9 +126,11 @@ def get_function(self, code_package: Benchmark) -> Function: "memory": memory, "timeout": timeout, "hash": code_package.hash, - "url": "WtfUrl" - } + "url": "WtfUrl", + }, + storage_config={ + "buckets": {"input": "input.buckets", "output": "output.buckets"} + }, ) - # FIXME: fix after dissociating code package and benchmark code_package.query_cache() - return FissionFunction(self) + return FissionFunction(func_name) diff --git a/sebs/fission/create_fission_function.sh b/sebs/fission/fissionBashScripts/create_fission_function.sh similarity index 100% rename from sebs/fission/create_fission_function.sh rename to sebs/fission/fissionBashScripts/create_fission_function.sh diff --git a/sebs/fission/run_fission.sh b/sebs/fission/fissionBashScripts/run_fission.sh similarity index 100% rename from sebs/fission/run_fission.sh rename to sebs/fission/fissionBashScripts/run_fission.sh diff --git a/sebs/fission/run_fission_function.sh b/sebs/fission/fissionBashScripts/run_fission_function.sh similarity index 100% rename from sebs/fission/run_fission_function.sh rename to sebs/fission/fissionBashScripts/run_fission_function.sh diff --git a/sebs/fission/update_fission_function.sh b/sebs/fission/fissionBashScripts/update_fission_function.sh similarity index 100% rename from sebs/fission/update_fission_function.sh rename to sebs/fission/fissionBashScripts/update_fission_function.sh diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 116212fc..3500b0a8 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -1,7 +1,4 @@ -from abc import ABC -from enum import Enum from sebs.faas.function import Function -from typing import List import subprocess From a1edfaef002642bac6236d075304a3856b533d52 Mon Sep 17 00:00:00 2001 From: Jakub Pajor Date: Thu, 18 Jun 2020 16:47:34 +0200 Subject: [PATCH 08/47] [fission_start_work] Fission k8s deployment written using python scripts --- sebs/fission/fission.py | 149 ++++++++++++++++-- .../create_fission_function.sh | 3 - .../fission/fissionBashScripts/run_fission.sh | 38 ----- .../run_fission_function.sh | 3 - .../update_fission_function.sh | 2 - sebs/fission/fissionFunction.py | 10 +- 6 files changed, 139 insertions(+), 66 deletions(-) delete mode 100755 sebs/fission/fissionBashScripts/create_fission_function.sh delete mode 100755 sebs/fission/fissionBashScripts/run_fission.sh delete mode 100755 sebs/fission/fissionBashScripts/run_fission_function.sh delete mode 100755 sebs/fission/fissionBashScripts/update_fission_function.sh diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 7d06b3ae..50e3cc5b 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -13,13 +13,115 @@ class Fission(System): + available_languages_images = {"python": "fission/python-env", "nodejs": "fission/node-env"} + def __init__( self, sebs_config: SeBSConfig, cache_client: Cache, docker_client: docker.client ): super().__init__(sebs_config, cache_client, docker_client) + self._added_functions: [str] = [] + + @staticmethod + def check_if_minikube_installed(): + try: + subprocess.run('minikube version'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError: + logging.error("ERROR: \"minikube\" required.") + + @staticmethod + def run_minikube(vm_driver='docker'): + try: + kube_status = subprocess.run('minikube status'.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + + # if minikube is already running, error will be raised to prevent to be started minikube again + subprocess.run( + 'grep Stopped'.split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=kube_status.stdout + ) + try: + logging.info('Starting minikube...') + subprocess.run(f'minikube start --vm-driver={vm_driver}'.split(), stdout=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError: + raise ChildProcessError + + except subprocess.CalledProcessError: + pass + except ChildProcessError: + logging.error("ERROR: COULDN'T START MINIKUBE") + exit(1) + + @staticmethod + def check_if_k8s_installed(): + try: + subprocess.run('kubectl version'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError: + logging.error("ERROR: \"kubectl\" required.") + + @staticmethod + def check_if_helm_installed(): + try: + subprocess.run('helm version'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError: + logging.error("ERROR: \"helm\" required.") + + @staticmethod + def install_fission_using_helm(k8s_namespace='fission'): + fission_url = 'https://github.com/fission/fission/releases/download/1.9.0/fission-all-1.9.0.tgz' + + try: + k8s_namespaces = subprocess.run( + 'kubectl get namespace'.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) + subprocess.run( + f'grep {k8s_namespace}'.split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=k8s_namespaces.stdout + ) + except subprocess.CalledProcessError: # if exception raised it means that there is no appropriate namespace + logging.info(f'No proper fission namespace... Installing Fission as \"{k8s_namespace}\"...') + subprocess.run( + f'kubectl create namespace {k8s_namespace}'.split(), + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True + ) + subprocess.run( + f'helm install --namespace {k8s_namespace} --name-template fission {fission_url}'.split(), + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True + ) + + @staticmethod + def install_fission_cli_if_needed(): + try: + subprocess.run(['fission'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError: # if raised - fission cli is not installed + logging.info("No fission CLI - installing...") + available_os = { + 'darwin': 'osx', + 'linux': 'linux' + } + import platform + fission_cli_url = f'https://github.com/fission/fission/releases/download/1.9.0/fission-cli-' \ + f'{available_os[platform.system().lower()]}' + + subprocess.run( + f'curl -Lo fission {fission_cli_url} && chmod +x fission && sudo mv fission /usr/local/bin/', + stdout=subprocess.DEVNULL, check=True + ) + + def initialize(self, config: Dict[str, str] = None): + if config is None: + config = {} - def initialize(self, config: Dict[str, str] = {}): - subprocess.call(["./fissionBashScripts/run_fission.sh"]) + Fission.check_if_minikube_installed() + Fission.run_minikube() + Fission.check_if_k8s_installed() + Fission.check_if_helm_installed() + Fission.install_fission_using_helm() + Fission.install_fission_cli_if_needed() def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: CONFIG_FILES = { @@ -37,25 +139,43 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: bytes_size = os.path.getsize(function_dir) return function_dir, bytes_size - def update_function(self, name: str, path: str): - subprocess.call(["./fissionBashScripts/update_fission_fuction.sh", name, path]) + def update_function(self, name: str, code_path: str): + subprocess.run( + f'fission fn update --name {name} --code {code_path}'.split(), + check=True, stdout=subprocess.DEVNULL + ) + + def create_env_if_needed(self, name: str, image: str): + try: + fission_env_list = subprocess.run( + 'fission env list '.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) - def create_function(self, name: str, language: str, path: str): - CONFIG_FILES = {"python": "fission/python-env", "nodejs": "fission/node-env"} + subprocess.run( + f'grep {name}'.split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=fission_env_list.stdout + ) + + except subprocess.CalledProcessError: # if exception raised it means that there is no appropriate namespace + logging.info(f'Creating env for {name} using image \"{image}\".') + subprocess.run( + f'fission env create --name {name} --image {image}'.split(), + check=True, stdout=subprocess.DEVNULL + ) - subprocess.call( - [ - "./fissionBashScripts/create_fission_function.sh", - name, - CONFIG_FILES[language], - path, - language, - ] + def create_function(self, name: str, env_name: str, path: str): + subprocess.run( + f'fission function create --name {name} --env {env_name} --code {path}'.split(), + check=True, stdout=subprocess.DEVNULL ) def get_function(self, code_package: Benchmark) -> Function: path, size = self.package_code(code_package) + # TODO: also exception if language not in self.available_languages_images if ( code_package.language_version not in self.system_config.supported_language_versions( @@ -111,6 +231,7 @@ def get_function(self, code_package: Benchmark) -> Function: func_name = "{}-{}-{}".format(benchmark, language, memory) + self.create_env_if_needed(language, self.available_languages_images[language]) self.create_function(func_name, language, path) self.cache_client.add_function( diff --git a/sebs/fission/fissionBashScripts/create_fission_function.sh b/sebs/fission/fissionBashScripts/create_fission_function.sh deleted file mode 100755 index 5c7c6086..00000000 --- a/sebs/fission/fissionBashScripts/create_fission_function.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -fission env create --name $4 --image $2 -fission function create --name $1 --env $4 --code $3 diff --git a/sebs/fission/fissionBashScripts/run_fission.sh b/sebs/fission/fissionBashScripts/run_fission.sh deleted file mode 100755 index a98c6746..00000000 --- a/sebs/fission/fissionBashScripts/run_fission.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -if ! minikube version -then - echo "ERROR: \"minikube\" required." - exit 1 -fi -minikube start --vm-driver=docker -if ! kubectl version -then - echo "ERROR: \"kubectl\" required." - exit 1 -fi -if ! helm version -then - echo "ERROR: \"helm\" required." - exit 1 -fi -export FISSION_NAMESPACE=fission-local-suu -if ! kubectl get namespace | grep $FISSION_NAMESPACE -then - echo "No proper fission namespace... Installing Fission as ${FISSION_NAMESPACE}..." - kubectl create namespace $FISSION_NAMESPACE - helm install --namespace $FISSION_NAMESPACE --name-template fission https://github.com/fission/fission/releases/download/1.9.0/fission-all-1.9.0.tgz -fi -if ! fission > /dev/null 2>&1 -then - echo "No fission CLI - installing..." - case $(uname | tr '[:upper:]' '[:lower:]') in - linux*) - export FISSION_OS_NAME=linux - ;; - darwin*) - export FISSION_OS_NAME=osx - ;; - esac - curl -Lo fission https://github.com/fission/fission/releases/download/1.9.0/fission-cli-${FISSION_OS_NAME} && chmod +x fission && sudo mv fission /usr/local/bin/ -fi -echo "Fission is ready to use" diff --git a/sebs/fission/fissionBashScripts/run_fission_function.sh b/sebs/fission/fissionBashScripts/run_fission_function.sh deleted file mode 100755 index 8f97e5e3..00000000 --- a/sebs/fission/fissionBashScripts/run_fission_function.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -fission function test --name $1 diff --git a/sebs/fission/fissionBashScripts/update_fission_function.sh b/sebs/fission/fissionBashScripts/update_fission_function.sh deleted file mode 100755 index 192f3c38..00000000 --- a/sebs/fission/fissionBashScripts/update_fission_function.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -fission fn update --name $1 --code $2 diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 3500b0a8..6447beeb 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -4,14 +4,12 @@ class FissionFunction(Function): def __init__(self, name: str): - self._name = name - - @property - def name(self): - return self._name + super().__init__(name) def sync_invoke(self, payload: dict): - subprocess.call(["./run_fission_function.sh", self.name]) + subprocess.run( + f'fission fn test --name {self.name}'.split(), check=True + ) def async_invoke(self, payload: dict): raise Exception("Non-trigger invoke not supported!") From 337314c9d2aa78a658081e9c4e76d1276ec8421d Mon Sep 17 00:00:00 2001 From: marcin Date: Sat, 20 Jun 2020 23:49:46 +0200 Subject: [PATCH 09/47] edit gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e707ac85..b5cbef12 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,6 @@ dmypy.json sebs-* # cache cache + +# ide +.vscode From 42de6c71a487ac38e81fb31c66c06aaeab5c8f7b Mon Sep 17 00:00:00 2001 From: Marcin Damek Date: Sun, 21 Jun 2020 12:28:03 +0200 Subject: [PATCH 10/47] manage fission start config --- config/fission.json | 15 +++++++++++++ sebs/fission/__init__.py | 3 ++- sebs/fission/config.py | 9 ++++++++ sebs/fission/fission.py | 46 +++++++++++++++++++++++++++------------- sebs/sebs.py | 6 ++++-- 5 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 config/fission.json create mode 100644 sebs/fission/config.py diff --git a/config/fission.json b/config/fission.json new file mode 100644 index 00000000..d28eefc1 --- /dev/null +++ b/config/fission.json @@ -0,0 +1,15 @@ +{ + "experiments": { + "update_code": false, + "update_storage": false, + "download_results": false, + "deployment": "fission", + "runtime": { + "language": "python", + "version": "3.6" + } + }, + "deployment": { + "name": "fission" + } +} diff --git a/sebs/fission/__init__.py b/sebs/fission/__init__.py index 1a03e037..a3f7abb9 100644 --- a/sebs/fission/__init__.py +++ b/sebs/fission/__init__.py @@ -1 +1,2 @@ -from .fission import Fission # noqa +from .fission import Fission #noqa +from .config import FissionConfig #noqa diff --git a/sebs/fission/config.py b/sebs/fission/config.py new file mode 100644 index 00000000..ad5b4279 --- /dev/null +++ b/sebs/fission/config.py @@ -0,0 +1,9 @@ +from sebs.faas.config import Config +from sebs.cache import Cache +class FissionConfig(Config): + def __init__(self): + pass + + @staticmethod + def initialize(config: dict, cache: Cache) -> Config: + pass \ No newline at end of file diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 50e3cc5b..acb33e10 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -4,50 +4,61 @@ import subprocess import docker from typing import Dict, Tuple +from sebs.faas.storage import PersistentStorage from sebs.cache import Cache from sebs.config import SeBSConfig from sebs.faas.function import Function from sebs.faas.system import System from sebs.fission.fissionFunction import FissionFunction from sebs.benchmark import Benchmark - +from sebs.fission.config import FissionConfig class Fission(System): available_languages_images = {"python": "fission/python-env", "nodejs": "fission/node-env"} def __init__( - self, sebs_config: SeBSConfig, cache_client: Cache, docker_client: docker.client + self, sebs_config: SeBSConfig,config: FissionConfig, cache_client: Cache, docker_client: docker.client ): super().__init__(sebs_config, cache_client, docker_client) self._added_functions: [str] = [] + @staticmethod + def name(): + return "fission" + + @property + def config(self) -> FissionConfig: + return self._config + @staticmethod def check_if_minikube_installed(): try: - subprocess.run('minikube version'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + subprocess.run('minikube version'.split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: logging.error("ERROR: \"minikube\" required.") @staticmethod def run_minikube(vm_driver='docker'): try: - kube_status = subprocess.run('minikube status'.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + kube_status = subprocess.run('minikube status'.split(), stdout=subprocess.PIPE) - # if minikube is already running, error will be raised to prevent to be started minikube again + #if minikube is already running, error will be raised to prevent to be started minikube again. subprocess.run( 'grep Stopped'.split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - input=kube_status.stdout + input=kube_status.stdout, + shell=True ) try: logging.info('Starting minikube...') - subprocess.run(f'minikube start --vm-driver={vm_driver}'.split(), stdout=subprocess.DEVNULL, check=True) + subprocess.run(f'minikube start --vm-driver={vm_driver}'.split(), check=True) except subprocess.CalledProcessError: raise ChildProcessError except subprocess.CalledProcessError: + logging.info('Minikube already working') pass except ChildProcessError: logging.error("ERROR: COULDN'T START MINIKUBE") @@ -63,7 +74,7 @@ def check_if_k8s_installed(): @staticmethod def check_if_helm_installed(): try: - subprocess.run('helm version'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + subprocess.run('helm version'.split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: logging.error("ERROR: \"helm\" required.") @@ -82,21 +93,20 @@ def install_fission_using_helm(k8s_namespace='fission'): stderr=subprocess.DEVNULL, input=k8s_namespaces.stdout ) - except subprocess.CalledProcessError: # if exception raised it means that there is no appropriate namespace + logging.info('fission namespace already exist') + except (subprocess.CalledProcessError): logging.info(f'No proper fission namespace... Installing Fission as \"{k8s_namespace}\"...') subprocess.run( - f'kubectl create namespace {k8s_namespace}'.split(), - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True + f'kubectl create namespace {k8s_namespace}'.split(), check=True ) subprocess.run( - f'helm install --namespace {k8s_namespace} --name-template fission {fission_url}'.split(), - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True + f'helm install --namespace {k8s_namespace} --name-template fission {fission_url}'.split(), check=True ) @staticmethod def install_fission_cli_if_needed(): try: - subprocess.run(['fission'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + subprocess.run(['fission'], check=True, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) except subprocess.CalledProcessError: # if raised - fission cli is not installed logging.info("No fission CLI - installing...") available_os = { @@ -109,9 +119,15 @@ def install_fission_cli_if_needed(): subprocess.run( f'curl -Lo fission {fission_cli_url} && chmod +x fission && sudo mv fission /usr/local/bin/', - stdout=subprocess.DEVNULL, check=True + stdout=subprocess.DEVNULL, check=True, shell=True ) + def shutdown(self) -> None: + pass + + def get_storage(self, replace_existing: bool = False) -> PersistentStorage: + pass + def initialize(self, config: Dict[str, str] = None): if config is None: config = {} diff --git a/sebs/sebs.py b/sebs/sebs.py index 18c6f993..bcd6274e 100644 --- a/sebs/sebs.py +++ b/sebs/sebs.py @@ -1,6 +1,8 @@ import docker from sebs.aws.aws import AWS, AWSConfig +from sebs.fission.fission import Fission +from sebs.fission.config import FissionConfig from sebs.cache import Cache from sebs.config import SeBSConfig from sebs.benchmark import Benchmark @@ -24,8 +26,8 @@ def __init__(self, cache_dir: str): def get_deployment(self, config: dict) -> FaasSystem: - implementations = {"aws": AWS} - configs = {"aws": AWSConfig.initialize} + implementations = {"aws": AWS, "fission": Fission} + configs = {"aws": AWSConfig.initialize, "fission": FissionConfig.initialize} name = config["name"] if name not in implementations: raise RuntimeError("Deployment {name} not supported!".format(**config)) From 3f51a5b6aed66fa568cf3858861b9a3584fd2819 Mon Sep 17 00:00:00 2001 From: marcin Date: Sun, 21 Jun 2020 01:51:44 +0200 Subject: [PATCH 11/47] add minio --- sebs/aws/s3.py | 2 +- sebs/fission/__init__.py | 1 + sebs/fission/fission.py | 10 +-- sebs/fission/minio.py | 130 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 sebs/fission/minio.py diff --git a/sebs/aws/s3.py b/sebs/aws/s3.py index f0d8568f..078ffc3a 100644 --- a/sebs/aws/s3.py +++ b/sebs/aws/s3.py @@ -95,7 +95,7 @@ def add_output_bucket( self, name: str, suffix: str = "output", cache: bool = True ) -> Tuple[str, int]: - idx = self.request_input_buckets + idx = self.request_input_buckets #are you sure? name = "{}-{}-{}".format(name, idx + 1, suffix) if cache: self.request_input_buckets += 1 diff --git a/sebs/fission/__init__.py b/sebs/fission/__init__.py index a3f7abb9..c872eec4 100644 --- a/sebs/fission/__init__.py +++ b/sebs/fission/__init__.py @@ -1,2 +1,3 @@ from .fission import Fission #noqa from .config import FissionConfig #noqa +from .minio import Minio #noqa diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index acb33e10..1ca2a1b0 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -12,12 +12,13 @@ from sebs.fission.fissionFunction import FissionFunction from sebs.benchmark import Benchmark from sebs.fission.config import FissionConfig +from sebs.fission.minio import Minio class Fission(System): available_languages_images = {"python": "fission/python-env", "nodejs": "fission/node-env"} - + storage : Minio def __init__( - self, sebs_config: SeBSConfig,config: FissionConfig, cache_client: Cache, docker_client: docker.client + self, sebs_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client ): super().__init__(sebs_config, cache_client, docker_client) self._added_functions: [str] = [] @@ -106,7 +107,7 @@ def install_fission_using_helm(k8s_namespace='fission'): @staticmethod def install_fission_cli_if_needed(): try: - subprocess.run(['fission'], check=True, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + subprocess.run(['fission'], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) except subprocess.CalledProcessError: # if raised - fission cli is not installed logging.info("No fission CLI - installing...") available_os = { @@ -126,7 +127,8 @@ def shutdown(self) -> None: pass def get_storage(self, replace_existing: bool = False) -> PersistentStorage: - pass + self.storage = Minio(self.docker_client) + return self.storage def initialize(self, config: Dict[str, str] = None): if config is None: diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py new file mode 100644 index 00000000..1acaaf7b --- /dev/null +++ b/sebs/fission/minio.py @@ -0,0 +1,130 @@ +from sebs.faas.storage import PersistentStorage +from typing import List, Tuple +import logging +import minio +import secrets +import docker +class Minio(PersistentStorage): + + storage_container = None + input_buckets = [] + output_buckets = [] + input_index = 0 + output_index = 0 + access_key = None + secret_key = None + port = 9000 + location = 'fissionBenchmark' + connection = None + docker_client = None + + def __init__(self, docker_client): + self.docker_client = docker_client + self.start() + self.connection = self.get_connection() + + def start(self): + self.access_key = secrets.token_urlsafe(32) + self.secret_key = secrets.token_hex(32) + logging.info('ACCESS_KEY={}'.format(self.access_key)) + logging.info('SECRET_KEY={}'.format(self.secret_key)) + self.storage_container = self.docker_client.containers.run( + 'minio/minio:latest', + command='server /data', + ports={str(self.port): self.port}, + environment={ + 'MINIO_ACCESS_KEY' : self.access_key, + 'MINIO_SECRET_KEY' : self.secret_key + }, + remove=True, + stdout=True, stderr=True, + detach=True + ) + self.storage_container.reload() + networks = self.storage_container.attrs['NetworkSettings']['Networks'] + self.url = '{IPAddress}:{Port}'.format( + IPAddress=networks['bridge']['IPAddress'], + Port=self.port + ) + logging.info('Starting minio instance at {}'.format(self.url)) + + def get_connection(self): + return minio.Minio(self.url, + access_key=self.access_key, + secret_key=self.secret_key, + secure=False) + + def input(self) -> List[str]: + return self.input_buckets + + def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: + input_index = self.input_index + bucket_name = '{}-{}-input'.format(name, input_index) + exist = self.connection.bucket_exist(bucket_name) + try: + if cache: + self.input_index += 1 + if exist: + return (bucket_name, input_index) + else: + self.connection.make_bucket(bucket_name, location=self.location) + input_buckets.append(bucket_name) + return (bucket_name, input_index) + if exist: + return (bucket_name, input_index) + self.connection.make_bucket(bucket_name, location=self.location) + return (bucket_name, input_index) + except (minio.error.BucketAlreadyOwnedByYou, minio.error.BucketAlreadyExists, minio.error.ResponseError) as err: + logging.error('Bucket creation failed!') + raise err + + def add_output_bucket(self, name: str, suffix: str = "output", cache: bool = True) -> Tuple[str, int]: + input_index = self.input_index + bucket_name = '{}-{}-{}'.format(name, input_index, suffix) + exist = self.connection.bucket_exist(bucket_name) + try: + if cache: + self.input_index += 1 + if exist: + return (bucket_name, input_index) + else: + self.connection.make_bucket(bucket_name, location=self.location) + input_buckets.append(bucket_name) + return (bucket_name, input_index) + if exist: + return (bucket_name, input_index) + self.connection.make_bucket(bucket_name, location=self.location) + return (bucket_name, input_index) + except (minio.error.BucketAlreadyOwnedByYou, minio.error.BucketAlreadyExists, minio.error.ResponseError) as err: + logging.error('Bucket creation failed!') + raise err + + def output(self) -> List[str]: + return self.output_buckets + + def download(self, bucket_name: str, key: str, filepath: str) -> None: + objects = self.connection.list_objects_v2(bucket) + objects = [obj.object_name for obj in objects] + for obj in objects: + self.connection.fget_object(bucket, obj, os.path.join(result_dir, obj)) + + def upload(self, bucket_name: str, filepath: str, key: str): + self.connection.put_object(bucket_name, filepath) + + def list_bucket(self, bucket_name: str) -> List[str]: + buckets = [] + for bucket in self.connection.list_buckets(): + if bucket.name == bucket_name: + buckets.append(bucket.name) + return buckets + + def allocate_buckets(self, benchmark: str, buckets: Tuple[int, int]): + inputNumber = buckets[0] + outputNumber = buckets[1] + for i in range(inputNumber): + self.add_input_bucket(benchmark) + for i in range(outputNumber): + self.add_output_bucket(benchmark) + + def uploader_func(self, bucket_idx: int, file: str, filepath: str) -> None: + pass From 6148f76f16810ed94d89782cd2f888222540c36f Mon Sep 17 00:00:00 2001 From: marcin Date: Sun, 21 Jun 2020 16:43:51 +0200 Subject: [PATCH 12/47] add packing --- config/systems.json | 31 ++++++++++++++++++++++ sebs/fission/fission.py | 57 ++++++++++++++++++++++++++++------------- sebs/fission/minio.py | 20 ++++++++++++--- 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/config/systems.json b/config/systems.json index a4d21ae5..3f79cbca 100644 --- a/config/systems.json +++ b/config/systems.json @@ -100,5 +100,36 @@ "username": "docker_user" } } + }, + "fission": { + "languages": { + "python": { + "base_images": { + "3.8": "lambci/lambda:build-python3.8", + "3.7": "lambci/lambda:build-python3.7", + "3.6": "lambci/lambda:build-python3.6" + }, + "versions": ["3.6", "3.7", "3.8"], + "images": ["build"], + "username": "docker_user", + "deployment": { + "files": [], + "packages": [] + } + }, + "nodejs": { + "base_images": { + "12.x" : "lambci/lambda:build-nodejs12.x", + "10.x" : "lambci/lambda:build-nodejs10.x" + }, + "versions": ["10.x", "12.x"], + "images": ["build"], + "username": "docker_user", + "deployment": { + "files": [], + "packages": [] + } + } + } } } diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 1ca2a1b0..b9c4762e 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -13,15 +13,18 @@ from sebs.benchmark import Benchmark from sebs.fission.config import FissionConfig from sebs.fission.minio import Minio +from sebs import utils class Fission(System): available_languages_images = {"python": "fission/python-env", "nodejs": "fission/node-env"} storage : Minio + _config : FissionConfig def __init__( self, sebs_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client ): super().__init__(sebs_config, cache_client, docker_client) self._added_functions: [str] = [] + self._config = config @staticmethod def name(): @@ -49,8 +52,7 @@ def run_minikube(vm_driver='docker'): check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - input=kube_status.stdout, - shell=True + input=kube_status.stdout ) try: logging.info('Starting minikube...') @@ -146,16 +148,21 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: "python": ["handler.py", "requirements.txt", ".python_packages"], "nodejs": ["handler.js", "package.json", "node_modules"], } - directory = benchmark.code_location + directory = benchmark.benchmark_path package_config = CONFIG_FILES[benchmark.language_name] function_dir = os.path.join(directory, "function") - os.makedirs(function_dir) - for file in os.listdir(directory): - if file not in package_config: - file = os.path.join(directory, file) - shutil.move(file, function_dir) + if os.path.exists(function_dir): + shutil.rmtree(function_dir) + shutil.copytree(directory, function_dir) + os.chdir(directory) + subprocess.run("zip -qu -r9 {}.zip * .".format(benchmark.benchmark).split()) + benchmark_archive = "{}.zip".format( + os.path.join(directory, benchmark.benchmark) + ) + logging.info("Created {} archive".format(benchmark_archive)) bytes_size = os.path.getsize(function_dir) - return function_dir, bytes_size + shutil.rmtree(function_dir) + return benchmark_archive, bytes_size def update_function(self, name: str, code_path: str): subprocess.run( @@ -185,10 +192,23 @@ def create_env_if_needed(self, name: str, image: str): ) def create_function(self, name: str, env_name: str, path: str): - subprocess.run( - f'fission function create --name {name} --env {env_name} --code {path}'.split(), - check=True, stdout=subprocess.DEVNULL - ) + validName = name.replace('.','-') + packageName = f'{validName}-package' + try: + packages = subprocess.run('fission package list'.split(), stdout=subprocess.PIPE, check=True) + subprocess.run( + f'grep {packageName}'.split(), + check=True, + input=packages.stdout + ) + except subprocess.CalledProcessError: + logging.info(f'Deploy fission package: {packageName}') + subprocess.run(f'fission package create --deployarchive {path} --name {packageName} --env {env_name}'.split(), check=True) + logging.info(f'Deploy fission function: {validName}') + subprocess.run( + f'fission fn create --name {validName} --pkg {packageName} --entrypoint "function.handler"'.split(), + check=True, stdout=subprocess.DEVNULL + ) def get_function(self, code_package: Benchmark) -> Function: path, size = self.package_code(code_package) @@ -241,7 +261,7 @@ def get_function(self, code_package: Benchmark) -> Function: ) return FissionFunction(func_name) else: - code_location = code_package.code_location + code_location = code_package.benchmark_path language = code_package.language_name language_runtime = code_package.language_version timeout = code_package.benchmark_config.timeout @@ -261,14 +281,15 @@ def get_function(self, code_package: Benchmark) -> Function: "name": func_name, "code_size": size, "runtime": language_runtime, - "role": "FissionRole", "memory": memory, "timeout": timeout, - "hash": code_package.hash, - "url": "WtfUrl", + "hash": code_package.hash }, storage_config={ - "buckets": {"input": "input.buckets", "output": "output.buckets"} + "buckets": { + "input": self.storage.input_buckets, + "output": self.storage.output_buckets + } }, ) code_package.query_cache() diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index 1acaaf7b..3ac1817d 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -24,18 +24,32 @@ def __init__(self, docker_client): self.connection = self.get_connection() def start(self): + minioName = 'minio' + try: + actualContainer = self.docker_client.containers.get(minioName) + actualContainer.rename('minio-to-kill') + actualContainer.stop() + actualContainer.wait() + self.startMinio(minioName) + except docker.errors.NotFound: + self.startMinio(minioName) + + def startMinio(self, minioName: str): + minioVersion = 'minio/minio:latest' self.access_key = secrets.token_urlsafe(32) self.secret_key = secrets.token_hex(32) + logging.info('Minio container starting') logging.info('ACCESS_KEY={}'.format(self.access_key)) - logging.info('SECRET_KEY={}'.format(self.secret_key)) + logging.info('SECRET_KEY={}'.format(self.secret_key)) self.storage_container = self.docker_client.containers.run( - 'minio/minio:latest', + minioVersion, command='server /data', ports={str(self.port): self.port}, environment={ 'MINIO_ACCESS_KEY' : self.access_key, 'MINIO_SECRET_KEY' : self.secret_key }, + name=minioName, remove=True, stdout=True, stderr=True, detach=True @@ -46,7 +60,7 @@ def start(self): IPAddress=networks['bridge']['IPAddress'], Port=self.port ) - logging.info('Starting minio instance at {}'.format(self.url)) + logging.info('Started minio instance at {}'.format(self.url)) def get_connection(self): return minio.Minio(self.url, From eb93174915fdcccc74b934f2c9ad1b138410b3a2 Mon Sep 17 00:00:00 2001 From: marcin Date: Sun, 21 Jun 2020 21:59:07 +0200 Subject: [PATCH 13/47] deploy fission function --- sebs/fission/config.py | 43 +++++++++++++++++++-- sebs/fission/fission.py | 83 ++++++++++++++++++++++++++++------------- 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/sebs/fission/config.py b/sebs/fission/config.py index ad5b4279..2d472d01 100644 --- a/sebs/fission/config.py +++ b/sebs/fission/config.py @@ -1,9 +1,46 @@ -from sebs.faas.config import Config +from sebs.faas.config import Config, Credentials, Resources from sebs.cache import Cache -class FissionConfig(Config): + + + +class FissionCredentials(Credentials): + def __init__(self): + pass + + def initialize(config: dict, cache: Cache) -> Credentials: + pass + + def serialize(self) -> dict: + pass + + +class FissionResources(Resources): def __init__(self): pass + + def serialize(self) -> dict: + pass + + def initialize(config: dict, cache: Cache) -> Resources: + pass + + +class FissionConfig(Config): + name: str + cache: Cache + def __init__(self, config: dict, cache: Cache): + self.name = config['name'] + self.cache = cache @staticmethod def initialize(config: dict, cache: Cache) -> Config: - pass \ No newline at end of file + return FissionConfig(config, cache) + + def credentials(self) -> Credentials: + pass + + def resources(self) -> Resources: + pass + + def serialize(self) -> dict: + pass diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index b9c4762e..6074e584 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -16,7 +16,8 @@ from sebs import utils class Fission(System): - available_languages_images = {"python": "fission/python-env", "nodejs": "fission/node-env"} + available_languages_images = {"python": "fission/python-env:latest", "nodejs": "fission/node-env:latest"} + available_languages_builders = {"python": "fission/python-builder:latest", "nodejs": "fission/node-builder:latest"} storage : Minio _config : FissionConfig def __init__( @@ -150,27 +151,22 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: } directory = benchmark.benchmark_path package_config = CONFIG_FILES[benchmark.language_name] - function_dir = os.path.join(directory, "function") - if os.path.exists(function_dir): - shutil.rmtree(function_dir) - shutil.copytree(directory, function_dir) os.chdir(directory) - subprocess.run("zip -qu -r9 {}.zip * .".format(benchmark.benchmark).split()) + subprocess.run("zip -jr {}.zip {}/".format(benchmark.benchmark, benchmark.language_name).split(), stdout=subprocess.DEVNULL) benchmark_archive = "{}.zip".format( os.path.join(directory, benchmark.benchmark) ) logging.info("Created {} archive".format(benchmark_archive)) - bytes_size = os.path.getsize(function_dir) - shutil.rmtree(function_dir) + bytes_size = os.path.getsize(os.path.join(directory, benchmark.language_name)) return benchmark_archive, bytes_size - def update_function(self, name: str, code_path: str): - subprocess.run( - f'fission fn update --name {name} --code {code_path}'.split(), - check=True, stdout=subprocess.DEVNULL - ) + def update_function(self, name: str, env_name: str, code_path: str): + packageName = f'{name}-package' + self.deleteFunction(name) + self.deletePackage(packageName) + self.createPackage(packageName, code_path, ) - def create_env_if_needed(self, name: str, image: str): + def create_env_if_needed(self, name: str, image: str, builder: str): try: fission_env_list = subprocess.run( 'fission env list '.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL @@ -187,13 +183,12 @@ def create_env_if_needed(self, name: str, image: str): except subprocess.CalledProcessError: # if exception raised it means that there is no appropriate namespace logging.info(f'Creating env for {name} using image \"{image}\".') subprocess.run( - f'fission env create --name {name} --image {image}'.split(), + f'fission env create --name {name} --image {image} --builder {builder}'.split(), check=True, stdout=subprocess.DEVNULL ) def create_function(self, name: str, env_name: str, path: str): - validName = name.replace('.','-') - packageName = f'{validName}-package' + packageName = f'{name}-package' try: packages = subprocess.run('fission package list'.split(), stdout=subprocess.PIPE, check=True) subprocess.run( @@ -201,15 +196,50 @@ def create_function(self, name: str, env_name: str, path: str): check=True, input=packages.stdout ) + try: + self.deletePackage(packageName) + self.createPackage(packageName, path, env_name) + functions = subprocess.run('fission function list'.split(), stdout=subprocess.PIPE, check=True) + subprocess.run( + f'grep {name}'.split(), + check=True, + input=functions.stdout + ) + except subprocess.CalledProcessError: + self.createFunction(packageName, name) except subprocess.CalledProcessError: - logging.info(f'Deploy fission package: {packageName}') - subprocess.run(f'fission package create --deployarchive {path} --name {packageName} --env {env_name}'.split(), check=True) - logging.info(f'Deploy fission function: {validName}') + self.createPackage(packageName, path, env_name) + self.createFunction(packageName, name) + + + def createPackage(self, packageName: str, path: str, envName: str) -> None: + logging.info(f'Deploying fission package...') + subprocess.run(f'fission package create --deployarchive {path} --name {packageName} --env {envName}'.split(), check=True) + + def deletePackage(self, packageName: str) -> None: + logging.info(f'Deleting fission package...') + subprocess.run(f'fission package delete --name {packageName}'.split()) + + def createFunction(self, packageName: str, name: str) -> None: + logging.info(f'Deploying fission function...') + subprocess.run( + f'fission fn create --name {name} --pkg {packageName} --entrypoint function.handler'.split(), check=True + ) + triggerName = f'{name}-trigger' + try: + triggers = subprocess.run(f'fission httptrigger list'.split()) subprocess.run( - f'fission fn create --name {validName} --pkg {packageName} --entrypoint "function.handler"'.split(), - check=True, stdout=subprocess.DEVNULL - ) + f'grep {triggerName}'.split(), + check=True, + input=packages.stdout + ) + except subprocess.CalledProcessError: + subprocess.run(f'fission httptrigger create --url /benchmark --method GET --name {triggerName} --function {name}'.split(), check=True) + def deleteFunction(self, name: str)-> None: + logging.info(f'Deleting fission function...') + subprocess.run(f'fission fn delete --name {packageName}'.split()) + def get_function(self, code_package: Benchmark) -> Function: path, size = self.package_code(code_package) @@ -226,7 +256,7 @@ def get_function(self, code_package: Benchmark) -> Function: version=code_package.language_version, ) ) - benchmark = code_package.benchmark + benchmark = code_package.benchmark.replace('.','-') if code_package.is_cached and code_package.is_cached_valid: func_name = code_package.cached_config["name"] @@ -236,6 +266,7 @@ def get_function(self, code_package: Benchmark) -> Function: fname=func_name, loc=code_location ) ) + self.create_function(func_name, code_package.language_name, code_location) return FissionFunction(func_name) elif code_package.is_cached: func_name = code_package.cached_config["name"] @@ -243,7 +274,7 @@ def get_function(self, code_package: Benchmark) -> Function: timeout = code_package.benchmark_config.timeout memory = code_package.benchmark_config.memory - self.update_function(func_name, path) + self.update_function(func_name, code_package.language_name, path) cached_cfg = code_package.cached_config cached_cfg["code_size"] = size @@ -269,7 +300,7 @@ def get_function(self, code_package: Benchmark) -> Function: func_name = "{}-{}-{}".format(benchmark, language, memory) - self.create_env_if_needed(language, self.available_languages_images[language]) + self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) self.create_function(func_name, language, path) self.cache_client.add_function( From e6e6286b78a60439c7947ae8fd7f1b31c1eccfb5 Mon Sep 17 00:00:00 2001 From: marcin Date: Sun, 21 Jun 2020 22:35:39 +0200 Subject: [PATCH 14/47] new benchmark --- benchmarks/000.microbenchmarks/020.adjustsleep/config.json | 5 +++++ benchmarks/000.microbenchmarks/020.adjustsleep/input.py | 5 +++++ .../000.microbenchmarks/020.adjustsleep/python/function.py | 5 +++++ sebs/fission/fission.py | 7 ++++--- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 benchmarks/000.microbenchmarks/020.adjustsleep/config.json create mode 100644 benchmarks/000.microbenchmarks/020.adjustsleep/input.py create mode 100644 benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py diff --git a/benchmarks/000.microbenchmarks/020.adjustsleep/config.json b/benchmarks/000.microbenchmarks/020.adjustsleep/config.json new file mode 100644 index 00000000..c7e8fc23 --- /dev/null +++ b/benchmarks/000.microbenchmarks/020.adjustsleep/config.json @@ -0,0 +1,5 @@ +{ + "timeout": 120, + "memory": 128, + "languages": ["python", "nodejs"] +} diff --git a/benchmarks/000.microbenchmarks/020.adjustsleep/input.py b/benchmarks/000.microbenchmarks/020.adjustsleep/input.py new file mode 100644 index 00000000..a2afe763 --- /dev/null +++ b/benchmarks/000.microbenchmarks/020.adjustsleep/input.py @@ -0,0 +1,5 @@ +def buckets_count(): + return (0, 0) + +def generate_input(data_dir, size, input_buckets, output_buckets, upload_func): + return 0 \ No newline at end of file diff --git a/benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py b/benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py new file mode 100644 index 00000000..4304058b --- /dev/null +++ b/benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py @@ -0,0 +1,5 @@ + +def handler(): + for i in range(10000): + print(i) + return "Done" diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 6074e584..0c5719b4 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -197,6 +197,7 @@ def create_function(self, name: str, env_name: str, path: str): input=packages.stdout ) try: + self.deleteFunction(name) self.deletePackage(packageName) self.createPackage(packageName, path, env_name) functions = subprocess.run('fission function list'.split(), stdout=subprocess.PIPE, check=True) @@ -227,18 +228,18 @@ def createFunction(self, packageName: str, name: str) -> None: ) triggerName = f'{name}-trigger' try: - triggers = subprocess.run(f'fission httptrigger list'.split()) + triggers = subprocess.run(f'fission httptrigger list'.split(), stdout=subprocess.PIPE, check=True) subprocess.run( f'grep {triggerName}'.split(), check=True, - input=packages.stdout + input=triggers.stdout ) except subprocess.CalledProcessError: subprocess.run(f'fission httptrigger create --url /benchmark --method GET --name {triggerName} --function {name}'.split(), check=True) def deleteFunction(self, name: str)-> None: logging.info(f'Deleting fission function...') - subprocess.run(f'fission fn delete --name {packageName}'.split()) + subprocess.run(f'fission fn delete --name {name}'.split()) def get_function(self, code_package: Benchmark) -> Function: path, size = self.package_code(code_package) From d6e9894f44dc6698798ed5742ea5feda39870524 Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 26 Jun 2020 21:43:44 +0200 Subject: [PATCH 15/47] cleanup and fix cache error --- sebs/fission/fission.py | 49 +++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 0c5719b4..f3650e11 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -19,6 +19,10 @@ class Fission(System): available_languages_images = {"python": "fission/python-env:latest", "nodejs": "fission/node-env:latest"} available_languages_builders = {"python": "fission/python-builder:latest", "nodejs": "fission/node-builder:latest"} storage : Minio + httpTriggerName: str + functionName: str + packageName: str + envName: str _config : FissionConfig def __init__( self, sebs_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client @@ -62,7 +66,18 @@ def run_minikube(vm_driver='docker'): raise ChildProcessError except subprocess.CalledProcessError: - logging.info('Minikube already working') + try: + subprocess.run( + 'grep unusually'.split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=kube_status.stdout + ) + logging.info('Starting minikube...') + subprocess.run(f'minikube start --vm-driver={vm_driver}'.split(), check=True) + except subprocess.CalledProcessError: + logging.info('Minikube already working') pass except ChildProcessError: logging.error("ERROR: COULDN'T START MINIKUBE") @@ -127,7 +142,10 @@ def install_fission_cli_if_needed(): ) def shutdown(self) -> None: - pass + subprocess.run(f'fission httptrigger delete --name {self.httpTriggerName}'.split()) + subprocess.run(f'fission fn delete --name {self.functionName}'.split()) + subprocess.run(f'fission package delete --name {self.packageName}'.split()) + subprocess.run(f'fission env delete --name {self.envName}'.split()) def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) @@ -179,9 +197,10 @@ def create_env_if_needed(self, name: str, image: str, builder: str): stderr=subprocess.DEVNULL, input=fission_env_list.stdout ) - + self.envName = name except subprocess.CalledProcessError: # if exception raised it means that there is no appropriate namespace logging.info(f'Creating env for {name} using image \"{image}\".') + self.envName = name subprocess.run( f'fission env create --name {name} --image {image} --builder {builder}'.split(), check=True, stdout=subprocess.DEVNULL @@ -215,6 +234,7 @@ def create_function(self, name: str, env_name: str, path: str): def createPackage(self, packageName: str, path: str, envName: str) -> None: logging.info(f'Deploying fission package...') + self.packageName = packageName subprocess.run(f'fission package create --deployarchive {path} --name {packageName} --env {envName}'.split(), check=True) def deletePackage(self, packageName: str) -> None: @@ -227,6 +247,8 @@ def createFunction(self, packageName: str, name: str) -> None: f'fission fn create --name {name} --pkg {packageName} --entrypoint function.handler'.split(), check=True ) triggerName = f'{name}-trigger' + self.functionName = name + self.httpTriggerName = triggerName try: triggers = subprocess.run(f'fission httptrigger list'.split(), stdout=subprocess.PIPE, check=True) subprocess.run( @@ -258,7 +280,10 @@ def get_function(self, code_package: Benchmark) -> Function: ) ) benchmark = code_package.benchmark.replace('.','-') - + language = code_package.language_name + language_runtime = code_package.language_version + timeout = code_package.benchmark_config.timeout + memory = code_package.benchmark_config.memory if code_package.is_cached and code_package.is_cached_valid: func_name = code_package.cached_config["name"] code_location = code_package.code_location @@ -267,14 +292,13 @@ def get_function(self, code_package: Benchmark) -> Function: fname=func_name, loc=code_location ) ) + self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) self.create_function(func_name, code_package.language_name, code_location) return FissionFunction(func_name) elif code_package.is_cached: func_name = code_package.cached_config["name"] code_location = code_package.code_location - timeout = code_package.benchmark_config.timeout - memory = code_package.benchmark_config.memory - + self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) self.update_function(func_name, code_package.language_name, path) cached_cfg = code_package.cached_config @@ -283,7 +307,7 @@ def get_function(self, code_package: Benchmark) -> Function: cached_cfg["memory"] = memory cached_cfg["hash"] = code_package.hash self.cache_client.update_function( - self.name(), benchmark, code_package.language_name, path, cached_cfg + self.name(), benchmark.replace('-','.'), code_package.language_name, path, cached_cfg ) code_package.query_cache() logging.info( @@ -294,19 +318,12 @@ def get_function(self, code_package: Benchmark) -> Function: return FissionFunction(func_name) else: code_location = code_package.benchmark_path - language = code_package.language_name - language_runtime = code_package.language_version - timeout = code_package.benchmark_config.timeout - memory = code_package.benchmark_config.memory - func_name = "{}-{}-{}".format(benchmark, language, memory) - self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) self.create_function(func_name, language, path) - self.cache_client.add_function( deployment=self.name(), - benchmark=benchmark, + benchmark=benchmark.replace('-','.'), language=language, code_package=path, language_config={ From 3caaa1246fbd00cc4ecc38df966cd27534114712 Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 26 Jun 2020 21:44:52 +0200 Subject: [PATCH 16/47] remove unnecessary benchmark --- benchmarks/000.microbenchmarks/020.adjustsleep/config.json | 5 ----- benchmarks/000.microbenchmarks/020.adjustsleep/input.py | 5 ----- .../000.microbenchmarks/020.adjustsleep/python/function.py | 5 ----- 3 files changed, 15 deletions(-) delete mode 100644 benchmarks/000.microbenchmarks/020.adjustsleep/config.json delete mode 100644 benchmarks/000.microbenchmarks/020.adjustsleep/input.py delete mode 100644 benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py diff --git a/benchmarks/000.microbenchmarks/020.adjustsleep/config.json b/benchmarks/000.microbenchmarks/020.adjustsleep/config.json deleted file mode 100644 index c7e8fc23..00000000 --- a/benchmarks/000.microbenchmarks/020.adjustsleep/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "timeout": 120, - "memory": 128, - "languages": ["python", "nodejs"] -} diff --git a/benchmarks/000.microbenchmarks/020.adjustsleep/input.py b/benchmarks/000.microbenchmarks/020.adjustsleep/input.py deleted file mode 100644 index a2afe763..00000000 --- a/benchmarks/000.microbenchmarks/020.adjustsleep/input.py +++ /dev/null @@ -1,5 +0,0 @@ -def buckets_count(): - return (0, 0) - -def generate_input(data_dir, size, input_buckets, output_buckets, upload_func): - return 0 \ No newline at end of file diff --git a/benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py b/benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py deleted file mode 100644 index 4304058b..00000000 --- a/benchmarks/000.microbenchmarks/020.adjustsleep/python/function.py +++ /dev/null @@ -1,5 +0,0 @@ - -def handler(): - for i in range(10000): - print(i) - return "Done" From 341dae3ef2b6b8e0920109a088163e18f466c247 Mon Sep 17 00:00:00 2001 From: marcin Date: Sat, 27 Jun 2020 01:00:27 +0200 Subject: [PATCH 17/47] fix package code directory --- benchmarks/wrappers/fission/python/handler.py | 2 ++ config/systems.json | 21 ++++++------------ sebs/fission/fission.py | 22 ++++++++++++++----- sebs/fission/minio.py | 1 - 4 files changed, 25 insertions(+), 21 deletions(-) create mode 100644 benchmarks/wrappers/fission/python/handler.py diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py new file mode 100644 index 00000000..abf4be5c --- /dev/null +++ b/benchmarks/wrappers/fission/python/handler.py @@ -0,0 +1,2 @@ +def handler(): + return "dsasdasdasdasdasdasdasdsa" diff --git a/config/systems.json b/config/systems.json index 3f79cbca..8d0568dc 100644 --- a/config/systems.json +++ b/config/systems.json @@ -104,29 +104,22 @@ "fission": { "languages": { "python": { - "base_images": { - "3.8": "lambci/lambda:build-python3.8", - "3.7": "lambci/lambda:build-python3.7", - "3.6": "lambci/lambda:build-python3.6" - }, - "versions": ["3.6", "3.7", "3.8"], - "images": ["build"], + "base_images": {"3.6":""}, + "versions": ["3.6"], + "images": [], "username": "docker_user", "deployment": { - "files": [], + "files": ["handler.py"], "packages": [] } }, "nodejs": { - "base_images": { - "12.x" : "lambci/lambda:build-nodejs12.x", - "10.x" : "lambci/lambda:build-nodejs10.x" - }, + "base_images": {"8":""}, "versions": ["10.x", "12.x"], - "images": ["build"], + "images": [], "username": "docker_user", "deployment": { - "files": [], + "files": ["handler.js"], "packages": [] } } diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index f3650e11..ddc83c00 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -163,19 +163,30 @@ def initialize(self, config: Dict[str, str] = None): Fission.install_fission_cli_if_needed() def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: + + benchmark.build() + CONFIG_FILES = { "python": ["handler.py", "requirements.txt", ".python_packages"], "nodejs": ["handler.js", "package.json", "node_modules"], } - directory = benchmark.benchmark_path + directory = benchmark.code_location package_config = CONFIG_FILES[benchmark.language_name] + function_dir = os.path.join(directory, "function") + os.makedirs(function_dir) + # move all files to 'function' except handler.py + for file in os.listdir(directory): + if file not in package_config: + file = os.path.join(directory, file) + shutil.move(file, function_dir) + cur_dir = os.getcwd() os.chdir(directory) - subprocess.run("zip -jr {}.zip {}/".format(benchmark.benchmark, benchmark.language_name).split(), stdout=subprocess.DEVNULL) + subprocess.run("zip -jr {}.zip * .".format(benchmark.benchmark).split(), stdout=subprocess.DEVNULL) benchmark_archive = "{}.zip".format( os.path.join(directory, benchmark.benchmark) ) logging.info("Created {} archive".format(benchmark_archive)) - bytes_size = os.path.getsize(os.path.join(directory, benchmark.language_name)) + bytes_size = os.path.getsize(benchmark_archive) return benchmark_archive, bytes_size def update_function(self, name: str, env_name: str, code_path: str): @@ -244,7 +255,7 @@ def deletePackage(self, packageName: str) -> None: def createFunction(self, packageName: str, name: str) -> None: logging.info(f'Deploying fission function...') subprocess.run( - f'fission fn create --name {name} --pkg {packageName} --entrypoint function.handler'.split(), check=True + f'fission fn create --name {name} --pkg {packageName} --entrypoint handler.handler'.split(), check=True ) triggerName = f'{name}-trigger' self.functionName = name @@ -257,7 +268,7 @@ def createFunction(self, packageName: str, name: str) -> None: input=triggers.stdout ) except subprocess.CalledProcessError: - subprocess.run(f'fission httptrigger create --url /benchmark --method GET --name {triggerName} --function {name}'.split(), check=True) + subprocess.run(f'fission httptrigger create --url /benchmark --method POST --name {triggerName} --function {name}'.split(), check=True) def deleteFunction(self, name: str)-> None: logging.info(f'Deleting fission function...') @@ -300,7 +311,6 @@ def get_function(self, code_package: Benchmark) -> Function: code_location = code_package.code_location self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) self.update_function(func_name, code_package.language_name, path) - cached_cfg = code_package.cached_config cached_cfg["code_size"] = size cached_cfg["timeout"] = timeout diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index 3ac1817d..d013b5bb 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -27,7 +27,6 @@ def start(self): minioName = 'minio' try: actualContainer = self.docker_client.containers.get(minioName) - actualContainer.rename('minio-to-kill') actualContainer.stop() actualContainer.wait() self.startMinio(minioName) From f434a2b407f8e7b505724c034f477fee973bc177 Mon Sep 17 00:00:00 2001 From: marcin Date: Tue, 30 Jun 2020 00:51:50 +0200 Subject: [PATCH 18/47] add handler --- benchmarks/wrappers/fission/python/handler.py | 9 ++++++++- sebs/fission/fission.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index abf4be5c..dd77cbe2 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -1,2 +1,9 @@ +from flask import request, jsonify +from function import function +import json def handler(): - return "dsasdasdasdasdasdasdasdsa" + body = request.get_data().decode("utf-8") + stringDict = json.dumps(body) + event = json.loads(stringDict) + functionResult = function.handler(event) + return jsonify(functionResult) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index ddc83c00..7d7f4c80 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -181,7 +181,7 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: shutil.move(file, function_dir) cur_dir = os.getcwd() os.chdir(directory) - subprocess.run("zip -jr {}.zip * .".format(benchmark.benchmark).split(), stdout=subprocess.DEVNULL) + subprocess.run("zip -r {}.zip ./".format(benchmark.benchmark).split(), stdout=subprocess.DEVNULL) benchmark_archive = "{}.zip".format( os.path.join(directory, benchmark.benchmark) ) From 954c5a48aebe3727c8d524d99c091c7b09ad3c26 Mon Sep 17 00:00:00 2001 From: marcin Date: Tue, 30 Jun 2020 23:45:26 +0200 Subject: [PATCH 19/47] delpoy function --- benchmarks/wrappers/fission/python/handler.py | 8 +++++--- sebs/fission/fission.py | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index dd77cbe2..e80c1de9 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -1,9 +1,11 @@ -from flask import request, jsonify +from flask import request, jsonify, current_app from function import function import json def handler(): body = request.get_data().decode("utf-8") - stringDict = json.dumps(body) - event = json.loads(stringDict) + current_app.logger.info("Body: " + body) + event = json.loads(body) + current_app.logger.info("Event: " + str(event)) functionResult = function.handler(event) + current_app.logger.info("Function result: " + str(functionResult)) return jsonify(functionResult) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 7d7f4c80..0ae82840 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -141,6 +141,14 @@ def install_fission_cli_if_needed(): stdout=subprocess.DEVNULL, check=True, shell=True ) + @staticmethod + def add_port_forwarding(port = 5051): + pass + # podName = subprocess.run(f'kubectl --namespace fission get pod -l svc=router -o name'.split(), stdout=subprocess.PIPE).stdout.decode("utf-8").rstrip() + # subprocess.Popen(f'kubectl --namespace fission port-forward {podName} {port}:8888'.split(), stderr=subprocess.DEVNULL) + # os.environ["FISSION_ROUTER"] = f"127.0.0.1:{port}" + #TODO: ustawic zmienna FISSION_ROUTER globalnie + def shutdown(self) -> None: subprocess.run(f'fission httptrigger delete --name {self.httpTriggerName}'.split()) subprocess.run(f'fission fn delete --name {self.functionName}'.split()) @@ -161,6 +169,7 @@ def initialize(self, config: Dict[str, str] = None): Fission.check_if_helm_installed() Fission.install_fission_using_helm() Fission.install_fission_cli_if_needed() + Fission.add_port_forwarding() def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: @@ -190,10 +199,7 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: return benchmark_archive, bytes_size def update_function(self, name: str, env_name: str, code_path: str): - packageName = f'{name}-package' - self.deleteFunction(name) - self.deletePackage(packageName) - self.createPackage(packageName, code_path, ) + self.create_function(name, env_name, code_path) def create_env_if_needed(self, name: str, image: str, builder: str): try: @@ -297,14 +303,14 @@ def get_function(self, code_package: Benchmark) -> Function: memory = code_package.benchmark_config.memory if code_package.is_cached and code_package.is_cached_valid: func_name = code_package.cached_config["name"] - code_location = code_package.code_location + code_location = os.path.join(code_package._cache_client.cache_dir, code_package._cached_config["code"]) logging.info( "Using cached function {fname} in {loc}".format( fname=func_name, loc=code_location ) ) self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) - self.create_function(func_name, code_package.language_name, code_location) + self.update_function(func_name, code_package.language_name, path) return FissionFunction(func_name) elif code_package.is_cached: func_name = code_package.cached_config["name"] From 68afc9d4400807d70f0022c2addaa2034cc0e262 Mon Sep 17 00:00:00 2001 From: marcin Date: Wed, 1 Jul 2020 00:15:27 +0200 Subject: [PATCH 20/47] add request invoke --- sebs/fission/fission.py | 2 +- sebs/fission/fissionFunction.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 0ae82840..2eff2e4c 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -261,7 +261,7 @@ def deletePackage(self, packageName: str) -> None: def createFunction(self, packageName: str, name: str) -> None: logging.info(f'Deploying fission function...') subprocess.run( - f'fission fn create --name {name} --pkg {packageName} --entrypoint handler.handler'.split(), check=True + f'fission fn create --name {name} --pkg {packageName} --entrypoint handler.handler --env {self.envName}'.split(), check=True ) triggerName = f'{name}-trigger' self.functionName = name diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 6447beeb..f5ddeabc 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -1,15 +1,21 @@ from sebs.faas.function import Function import subprocess - +import json +import requests +import os class FissionFunction(Function): def __init__(self, name: str): super().__init__(name) def sync_invoke(self, payload: dict): - subprocess.run( - f'fission fn test --name {self.name}'.split(), check=True - ) + #TODO:dokonczyc + #functionUrl = os.environ['FISSION_ROUTER'] + url = "http://localhost:5051/benchmark" + payload = json.dumps(payload) + headers = {'content-type': "application/json"} + response = requests.request("POST", url, data=payload, headers=headers) + return response def async_invoke(self, payload: dict): raise Exception("Non-trigger invoke not supported!") From 460ca18adcfd1f7547b4c9151f491650a7a33b03 Mon Sep 17 00:00:00 2001 From: marcin Date: Wed, 1 Jul 2020 00:50:20 +0200 Subject: [PATCH 21/47] add requirements handling --- sebs/fission/fission.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 2eff2e4c..4423d66d 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -3,6 +3,7 @@ import shutil import subprocess import docker +import stat from typing import Dict, Tuple from sebs.faas.storage import PersistentStorage from sebs.cache import Cache @@ -23,6 +24,7 @@ class Fission(System): functionName: str packageName: str envName: str + shoudCallBuilder: bool _config : FissionConfig def __init__( self, sebs_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client @@ -176,14 +178,21 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: benchmark.build() CONFIG_FILES = { - "python": ["handler.py", "requirements.txt", ".python_packages"], + "python": ["handler.py", "requirements.txt", ".python_packages", "build.sh"], "nodejs": ["handler.js", "package.json", "node_modules"], } directory = benchmark.code_location package_config = CONFIG_FILES[benchmark.language_name] function_dir = os.path.join(directory, "function") os.makedirs(function_dir) - # move all files to 'function' except handler.py + if os.path.exists(os.path.join(directory, "requirements.txt")): + scriptPath = os.path.join(directory,"build.sh") + self.shoudCallBuilder = True + f = open(scriptPath, "w+") + f.write("pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}") + f.close() + st = os.stat(scriptPath) + os.chmod(scriptPath, st.st_mode | stat.S_IEXEC) for file in os.listdir(directory): if file not in package_config: file = os.path.join(directory, file) @@ -252,7 +261,10 @@ def create_function(self, name: str, env_name: str, path: str): def createPackage(self, packageName: str, path: str, envName: str) -> None: logging.info(f'Deploying fission package...') self.packageName = packageName - subprocess.run(f'fission package create --deployarchive {path} --name {packageName} --env {envName}'.split(), check=True) + process = f'fission package create --deployarchive {path} --name {packageName} --env {envName}' + if self.shoudCallBuilder: + process.join(' --buildcmd ./build.sh') + subprocess.run(process.split(), check=True) def deletePackage(self, packageName: str) -> None: logging.info(f'Deleting fission package...') From 77004ff8f0e8c1c3bf63520cd283320616ba7c34 Mon Sep 17 00:00:00 2001 From: marcin Date: Wed, 1 Jul 2020 22:08:20 +0200 Subject: [PATCH 22/47] extend handler --- benchmarks/wrappers/fission/python/handler.py | 29 +++++++++++++++---- sebs/fission/fission.py | 2 +- sebs/fission/fissionFunction.py | 20 +++++++++++-- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index e80c1de9..8f3ab439 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -1,11 +1,30 @@ from flask import request, jsonify, current_app -from function import function -import json + +import json, datetime def handler(): body = request.get_data().decode("utf-8") current_app.logger.info("Body: " + body) event = json.loads(body) current_app.logger.info("Event: " + str(event)) - functionResult = function.handler(event) - current_app.logger.info("Function result: " + str(functionResult)) - return jsonify(functionResult) + begin = datetime.datetime.now() + from function import function + ret = function.handler(event) + end = datetime.datetime.now() + current_app.logger.info("Function result: " + str(ret)) + log_data = { + 'result': ret['result'] + } + if 'measurement' in ret: + log_data['measurement'] = ret['measurement'] + results_time = 0 + # how to check it? + is_cold = False + return jsonify( + json.dumps({ + 'begin': begin.strftime('%s.%f'), + 'end': end.strftime('%s.%f'), + 'results_time': results_time, + 'is_cold': is_cold, + 'result': log_data + })) + diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 4423d66d..a3401988 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -24,7 +24,7 @@ class Fission(System): functionName: str packageName: str envName: str - shoudCallBuilder: bool + shoudCallBuilder: bool = False _config : FissionConfig def __init__( self, sebs_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index f5ddeabc..c709b3e5 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -1,6 +1,7 @@ -from sebs.faas.function import Function +from sebs.faas.function import Function, ExecutionResult import subprocess import json +import datetime import requests import os @@ -14,8 +15,23 @@ def sync_invoke(self, payload: dict): url = "http://localhost:5051/benchmark" payload = json.dumps(payload) headers = {'content-type': "application/json"} + begin = datetime.datetime.now() response = requests.request("POST", url, data=payload, headers=headers) - return response + end = datetime.datetime.now() + fissionResult = ExecutionResult(begin, end) + if response.status_code != 200: + logging.error("Invocation of {} failed!".format(self.name)) + logging.error("Input: {}".format(payload)) + self._deployment.get_invocation_error( + function_name=self.name, + start_time=int(begin.strftime("%s")) - 1, + end_time=int(end.strftime("%s")) + 1, + ) + fissionResult.stats.failure = True + return fissionResult + returnContent = json.loads(json.loads(response.content)) + + return fissionResult def async_invoke(self, payload: dict): raise Exception("Non-trigger invoke not supported!") From 0ddaded96648b929eedd57b8cca57796e7e73d84 Mon Sep 17 00:00:00 2001 From: marcin Date: Wed, 1 Jul 2020 23:34:32 +0200 Subject: [PATCH 23/47] fix bugs with shutdown --- sebs/fission/fission.py | 25 +++++++++++++------------ sebs/fission/fissionFunction.py | 3 +-- sebs/fission/minio.py | 1 + 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index a3401988..960521a0 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -24,7 +24,7 @@ class Fission(System): functionName: str packageName: str envName: str - shoudCallBuilder: bool = False + shouldCallBuilder: bool = False _config : FissionConfig def __init__( self, sebs_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client @@ -145,17 +145,18 @@ def install_fission_cli_if_needed(): @staticmethod def add_port_forwarding(port = 5051): - pass - # podName = subprocess.run(f'kubectl --namespace fission get pod -l svc=router -o name'.split(), stdout=subprocess.PIPE).stdout.decode("utf-8").rstrip() - # subprocess.Popen(f'kubectl --namespace fission port-forward {podName} {port}:8888'.split(), stderr=subprocess.DEVNULL) - # os.environ["FISSION_ROUTER"] = f"127.0.0.1:{port}" - #TODO: ustawic zmienna FISSION_ROUTER globalnie + podName = subprocess.run(f'kubectl --namespace fission get pod -l svc=router -o name'.split(), stdout=subprocess.PIPE).stdout.decode("utf-8").rstrip() + subprocess.Popen(f'kubectl --namespace fission port-forward {podName} {port}:8888'.split(), stderr=subprocess.DEVNULL) def shutdown(self) -> None: - subprocess.run(f'fission httptrigger delete --name {self.httpTriggerName}'.split()) - subprocess.run(f'fission fn delete --name {self.functionName}'.split()) - subprocess.run(f'fission package delete --name {self.packageName}'.split()) - subprocess.run(f'fission env delete --name {self.envName}'.split()) + if hasattr(self, "httpTriggerName"): + subprocess.run(f'fission httptrigger delete --name {self.httpTriggerName}'.split()) + if hasattr(self, "functionName"): + subprocess.run(f'fission fn delete --name {self.functionName}'.split()) + if hasattr(self, "packageName"): + subprocess.run(f'fission package delete --name {self.packageName}'.split()) + if hasattr(self, "envName"): + subprocess.run(f'fission env delete --name {self.envName}'.split()) def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) @@ -187,7 +188,7 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: os.makedirs(function_dir) if os.path.exists(os.path.join(directory, "requirements.txt")): scriptPath = os.path.join(directory,"build.sh") - self.shoudCallBuilder = True + self.shouldCallBuilder = True f = open(scriptPath, "w+") f.write("pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}") f.close() @@ -262,7 +263,7 @@ def createPackage(self, packageName: str, path: str, envName: str) -> None: logging.info(f'Deploying fission package...') self.packageName = packageName process = f'fission package create --deployarchive {path} --name {packageName} --env {envName}' - if self.shoudCallBuilder: + if self.shouldCallBuilder: process.join(' --buildcmd ./build.sh') subprocess.run(process.split(), check=True) diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index c709b3e5..7cf03114 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -4,14 +4,13 @@ import datetime import requests import os +import logging class FissionFunction(Function): def __init__(self, name: str): super().__init__(name) def sync_invoke(self, payload: dict): - #TODO:dokonczyc - #functionUrl = os.environ['FISSION_ROUTER'] url = "http://localhost:5051/benchmark" payload = json.dumps(payload) headers = {'content-type': "application/json"} diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index d013b5bb..13a929f4 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -29,6 +29,7 @@ def start(self): actualContainer = self.docker_client.containers.get(minioName) actualContainer.stop() actualContainer.wait() + actualContainer.reload() self.startMinio(minioName) except docker.errors.NotFound: self.startMinio(minioName) From d0775cdfd9cd7ad45c4f731c6b2d55a0dc33c6d8 Mon Sep 17 00:00:00 2001 From: Jakub Pajor Date: Thu, 2 Jul 2020 00:05:17 +0200 Subject: [PATCH 24/47] [feature_fission] cold start and some comments added --- benchmarks/wrappers/fission/python/handler.py | 12 +++++++++--- sebs/fission/fissionFunction.py | 13 ++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index 8f3ab439..569ae9e7 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -16,9 +16,16 @@ def handler(): } if 'measurement' in ret: log_data['measurement'] = ret['measurement'] - results_time = 0 - # how to check it? + + results_time = (end - begin) / datetime.timedelta(microseconds=1) + + # cold test is_cold = False + fname = os.path.join('/tmp', 'cold_run') + if not os.path.exists(fname): + is_cold = True + open(fname, 'a').close() + return jsonify( json.dumps({ 'begin': begin.strftime('%s.%f'), @@ -27,4 +34,3 @@ def handler(): 'is_cold': is_cold, 'result': log_data })) - diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 7cf03114..abf453ef 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -21,11 +21,14 @@ def sync_invoke(self, payload: dict): if response.status_code != 200: logging.error("Invocation of {} failed!".format(self.name)) logging.error("Input: {}".format(payload)) - self._deployment.get_invocation_error( - function_name=self.name, - start_time=int(begin.strftime("%s")) - 1, - end_time=int(end.strftime("%s")) + 1, - ) + + # TODO: this part is form AWS, need to be rethink + # self._deployment.get_invocation_error( + # function_name=self.name, + # start_time=int(begin.strftime("%s")) - 1, + # end_time=int(end.strftime("%s")) + 1, + # ) + fissionResult.stats.failure = True return fissionResult returnContent = json.loads(json.loads(response.content)) From 7d451e9cdb8bb62c96ed11c6f80c4d3e200de255 Mon Sep 17 00:00:00 2001 From: marcin Date: Thu, 2 Jul 2020 00:07:09 +0200 Subject: [PATCH 25/47] add logging --- sebs/fission/fission.py | 7 +++++++ sebs/fission/fissionFunction.py | 4 +++- sebs/fission/minio.py | 1 - 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 960521a0..b4dcd79b 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -157,6 +157,13 @@ def shutdown(self) -> None: subprocess.run(f'fission package delete --name {self.packageName}'.split()) if hasattr(self, "envName"): subprocess.run(f'fission env delete --name {self.envName}'.split()) + try: + minioContainer = self.docker_client.containers.get("minio") + minioContainer.stop() + except docker.errors.NotFound: + pass + logging.info("Minio stopped") + def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 7cf03114..f5a1d6bd 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -15,8 +15,10 @@ def sync_invoke(self, payload: dict): payload = json.dumps(payload) headers = {'content-type': "application/json"} begin = datetime.datetime.now() + logging.info(f"Function {self.name} invoking...") response = requests.request("POST", url, data=payload, headers=headers) end = datetime.datetime.now() + logging.info(f"Function {self.name} returned response with code: {response.status_code}") fissionResult = ExecutionResult(begin, end) if response.status_code != 200: logging.error("Invocation of {} failed!".format(self.name)) @@ -29,7 +31,7 @@ def sync_invoke(self, payload: dict): fissionResult.stats.failure = True return fissionResult returnContent = json.loads(json.loads(response.content)) - + fissionResult.parse_benchmark_output(returnContent) return fissionResult def async_invoke(self, payload: dict): diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index 13a929f4..d013b5bb 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -29,7 +29,6 @@ def start(self): actualContainer = self.docker_client.containers.get(minioName) actualContainer.stop() actualContainer.wait() - actualContainer.reload() self.startMinio(minioName) except docker.errors.NotFound: self.startMinio(minioName) From a6415e1b0da1105e84b8f0b1e58a62b6844c399d Mon Sep 17 00:00:00 2001 From: marcin Date: Thu, 2 Jul 2020 00:20:23 +0200 Subject: [PATCH 26/47] cold_start fix --- benchmarks/wrappers/fission/python/handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index 569ae9e7..9869f1de 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -1,6 +1,6 @@ from flask import request, jsonify, current_app -import json, datetime +import json, datetime, os def handler(): body = request.get_data().decode("utf-8") current_app.logger.info("Body: " + body) @@ -21,7 +21,7 @@ def handler(): # cold test is_cold = False - fname = os.path.join('/tmp', 'cold_run') + fname = 'cold_run' if not os.path.exists(fname): is_cold = True open(fname, 'a').close() From e88c2552d1be1f4a586e23399a5659e810284d76 Mon Sep 17 00:00:00 2001 From: marcin Date: Thu, 2 Jul 2020 01:13:27 +0200 Subject: [PATCH 27/47] linting --- sebs/fission/__init__.py | 6 +- sebs/fission/config.py | 14 +- sebs/fission/fission.py | 319 +++++++++++++++++++++----------- sebs/fission/fissionFunction.py | 15 +- sebs/fission/minio.py | 81 ++++---- 5 files changed, 276 insertions(+), 159 deletions(-) diff --git a/sebs/fission/__init__.py b/sebs/fission/__init__.py index c872eec4..b1353c64 100644 --- a/sebs/fission/__init__.py +++ b/sebs/fission/__init__.py @@ -1,3 +1,3 @@ -from .fission import Fission #noqa -from .config import FissionConfig #noqa -from .minio import Minio #noqa +from .fission import Fission # noqa +from .config import FissionConfig # noqa +from .minio import Minio # noqa diff --git a/sebs/fission/config.py b/sebs/fission/config.py index 2d472d01..d2ff5b9d 100644 --- a/sebs/fission/config.py +++ b/sebs/fission/config.py @@ -2,14 +2,14 @@ from sebs.cache import Cache - class FissionCredentials(Credentials): def __init__(self): pass + @staticmethod def initialize(config: dict, cache: Cache) -> Credentials: - pass - + return FissionCredentials() + def serialize(self) -> dict: pass @@ -19,8 +19,9 @@ def __init__(self): pass def serialize(self) -> dict: - pass + return {"": ""} + @staticmethod def initialize(config: dict, cache: Cache) -> Resources: pass @@ -28,10 +29,11 @@ def initialize(config: dict, cache: Cache) -> Resources: class FissionConfig(Config): name: str cache: Cache + def __init__(self, config: dict, cache: Cache): - self.name = config['name'] + self.name = config["name"] self.cache = cache - + @staticmethod def initialize(config: dict, cache: Cache) -> Config: return FissionConfig(config, cache) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index b4dcd79b..7d004f04 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -4,7 +4,7 @@ import subprocess import docker import stat -from typing import Dict, Tuple +from typing import Dict, Tuple, List from sebs.faas.storage import PersistentStorage from sebs.cache import Cache from sebs.config import SeBSConfig @@ -14,23 +14,34 @@ from sebs.benchmark import Benchmark from sebs.fission.config import FissionConfig from sebs.fission.minio import Minio -from sebs import utils + class Fission(System): - available_languages_images = {"python": "fission/python-env:latest", "nodejs": "fission/node-env:latest"} - available_languages_builders = {"python": "fission/python-builder:latest", "nodejs": "fission/node-builder:latest"} - storage : Minio + available_languages_images = { + "python": "fission/python-env:latest", + "nodejs": "fission/node-env:latest", + } + available_languages_builders = { + "python": "fission/python-builder:latest", + "nodejs": "fission/node-builder:latest", + } + storage: Minio httpTriggerName: str functionName: str packageName: str envName: str shouldCallBuilder: bool = False - _config : FissionConfig + _config: FissionConfig + def __init__( - self, sebs_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client + self, + sebs_config: SeBSConfig, + config: FissionConfig, + cache_client: Cache, + docker_client: docker.client, ): super().__init__(sebs_config, cache_client, docker_client) - self._added_functions: [str] = [] + self._added_functions: List[str] = [] self._config = config @staticmethod @@ -44,42 +55,54 @@ def config(self) -> FissionConfig: @staticmethod def check_if_minikube_installed(): try: - subprocess.run('minikube version'.split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run( + "minikube version".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) except subprocess.CalledProcessError: - logging.error("ERROR: \"minikube\" required.") + logging.error('ERROR: "minikube" required.') @staticmethod - def run_minikube(vm_driver='docker'): + def run_minikube(vm_driver="docker"): try: - kube_status = subprocess.run('minikube status'.split(), stdout=subprocess.PIPE) + kube_status = subprocess.run( + "minikube status".split(), stdout=subprocess.PIPE + ) - #if minikube is already running, error will be raised to prevent to be started minikube again. + # if minikube is already running, + # error will be raised to prevent to be started minikube again. subprocess.run( - 'grep Stopped'.split(), + "grep Stopped".split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - input=kube_status.stdout + input=kube_status.stdout, ) try: - logging.info('Starting minikube...') - subprocess.run(f'minikube start --vm-driver={vm_driver}'.split(), check=True) + logging.info("Starting minikube...") + subprocess.run( + f"minikube start --vm-driver={vm_driver}".split(), check=True + ) except subprocess.CalledProcessError: raise ChildProcessError except subprocess.CalledProcessError: try: subprocess.run( - 'grep unusually'.split(), - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - input=kube_status.stdout - ) - logging.info('Starting minikube...') - subprocess.run(f'minikube start --vm-driver={vm_driver}'.split(), check=True) + "grep unusually".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=kube_status.stdout, + ) + logging.info("Starting minikube...") + subprocess.run( + f"minikube start --vm-driver={vm_driver}".split(), check=True + ) except subprocess.CalledProcessError: - logging.info('Minikube already working') + logging.info("Minikube already working") pass except ChildProcessError: logging.error("ERROR: COULDN'T START MINIKUBE") @@ -88,83 +111,120 @@ def run_minikube(vm_driver='docker'): @staticmethod def check_if_k8s_installed(): try: - subprocess.run('kubectl version'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + subprocess.run( + "kubectl version".split(), + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) except subprocess.CalledProcessError: - logging.error("ERROR: \"kubectl\" required.") + logging.error('ERROR: "kubectl" required.') @staticmethod def check_if_helm_installed(): try: - subprocess.run('helm version'.split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run( + "helm version".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) except subprocess.CalledProcessError: - logging.error("ERROR: \"helm\" required.") + logging.error('ERROR: "helm" required.') @staticmethod - def install_fission_using_helm(k8s_namespace='fission'): - fission_url = 'https://github.com/fission/fission/releases/download/1.9.0/fission-all-1.9.0.tgz' + def install_fission_using_helm(k8s_namespace="fission"): + fission_url = "https://github.com/fission/fission/releases/download/1.9.0/fission-all-1.9.0.tgz" # noqa: E501 try: k8s_namespaces = subprocess.run( - 'kubectl get namespace'.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + "kubectl get namespace".split(), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, ) subprocess.run( - f'grep {k8s_namespace}'.split(), + f"grep {k8s_namespace}".split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - input=k8s_namespaces.stdout + input=k8s_namespaces.stdout, ) - logging.info('fission namespace already exist') + logging.info("fission namespace already exist") except (subprocess.CalledProcessError): - logging.info(f'No proper fission namespace... Installing Fission as \"{k8s_namespace}\"...') + logging.info( + f'No proper fission namespace.Installing Fission as "{k8s_namespace}.."' + ) subprocess.run( - f'kubectl create namespace {k8s_namespace}'.split(), check=True + f"kubectl create namespace {k8s_namespace}".split(), check=True ) subprocess.run( - f'helm install --namespace {k8s_namespace} --name-template fission {fission_url}'.split(), check=True + f"helm install --namespace {k8s_namespace} \ + --name-template fission {fission_url}".split(), + check=True, ) @staticmethod def install_fission_cli_if_needed(): try: - subprocess.run(['fission'], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) - except subprocess.CalledProcessError: # if raised - fission cli is not installed + subprocess.run( + ["fission"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + shell=True, + ) + except subprocess.CalledProcessError: # fission cli is not installed logging.info("No fission CLI - installing...") - available_os = { - 'darwin': 'osx', - 'linux': 'linux' - } + available_os = {"darwin": "osx", "linux": "linux"} import platform - fission_cli_url = f'https://github.com/fission/fission/releases/download/1.9.0/fission-cli-' \ - f'{available_os[platform.system().lower()]}' + + fission_cli_url = ( + f"https://github.com/fission/fission/releases/download/1.9.0/fission-cli-" # noqa: E501 + f"{available_os[platform.system().lower()]}" + ) subprocess.run( - f'curl -Lo fission {fission_cli_url} && chmod +x fission && sudo mv fission /usr/local/bin/', - stdout=subprocess.DEVNULL, check=True, shell=True + f"curl -Lo fission {fission_cli_url} \ + && chmod +x fission \ + && sudo mv fission /usr/local/bin/", + stdout=subprocess.DEVNULL, + check=True, + shell=True, ) @staticmethod - def add_port_forwarding(port = 5051): - podName = subprocess.run(f'kubectl --namespace fission get pod -l svc=router -o name'.split(), stdout=subprocess.PIPE).stdout.decode("utf-8").rstrip() - subprocess.Popen(f'kubectl --namespace fission port-forward {podName} {port}:8888'.split(), stderr=subprocess.DEVNULL) + def add_port_forwarding(port=5051): + podName = ( + subprocess.run( + f"kubectl --namespace fission get pod -l svc=router -o name".split(), + stdout=subprocess.PIPE, + ) + .stdout.decode("utf-8") + .rstrip() + ) + subprocess.Popen( + f"kubectl --namespace fission port-forward {podName} {port}:8888".split(), + stderr=subprocess.DEVNULL, + ) def shutdown(self) -> None: if hasattr(self, "httpTriggerName"): - subprocess.run(f'fission httptrigger delete --name {self.httpTriggerName}'.split()) + subprocess.run( + f"fission httptrigger delete --name {self.httpTriggerName}".split() + ) if hasattr(self, "functionName"): - subprocess.run(f'fission fn delete --name {self.functionName}'.split()) + subprocess.run(f"fission fn delete --name {self.functionName}".split()) if hasattr(self, "packageName"): - subprocess.run(f'fission package delete --name {self.packageName}'.split()) + subprocess.run(f"fission package delete --name {self.packageName}".split()) if hasattr(self, "envName"): - subprocess.run(f'fission env delete --name {self.envName}'.split()) + subprocess.run(f"fission env delete --name {self.envName}".split()) try: minioContainer = self.docker_client.containers.get("minio") - minioContainer.stop() + minioContainer.stop() except docker.errors.NotFound: pass logging.info("Minio stopped") - def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) return self.storage @@ -186,7 +246,12 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: benchmark.build() CONFIG_FILES = { - "python": ["handler.py", "requirements.txt", ".python_packages", "build.sh"], + "python": [ + "handler.py", + "requirements.txt", + ".python_packages", + "build.sh", + ], "nodejs": ["handler.js", "package.json", "node_modules"], } directory = benchmark.code_location @@ -194,10 +259,13 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: function_dir = os.path.join(directory, "function") os.makedirs(function_dir) if os.path.exists(os.path.join(directory, "requirements.txt")): - scriptPath = os.path.join(directory,"build.sh") + scriptPath = os.path.join(directory, "build.sh") self.shouldCallBuilder = True f = open(scriptPath, "w+") - f.write("pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}") + f.write( + "pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && \ + cp -r ${SRC_PKG} ${DEPLOY_PKG}" + ) f.close() st = os.stat(scriptPath) os.chmod(scriptPath, st.st_mode | stat.S_IEXEC) @@ -205,9 +273,11 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: if file not in package_config: file = os.path.join(directory, file) shutil.move(file, function_dir) - cur_dir = os.getcwd() os.chdir(directory) - subprocess.run("zip -r {}.zip ./".format(benchmark.benchmark).split(), stdout=subprocess.DEVNULL) + subprocess.run( + "zip -r {}.zip ./".format(benchmark.benchmark).split(), + stdout=subprocess.DEVNULL, + ) benchmark_archive = "{}.zip".format( os.path.join(directory, benchmark.benchmark) ) @@ -221,85 +291,95 @@ def update_function(self, name: str, env_name: str, code_path: str): def create_env_if_needed(self, name: str, image: str, builder: str): try: fission_env_list = subprocess.run( - 'fission env list '.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + "fission env list ".split(), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, ) subprocess.run( - f'grep {name}'.split(), + f"grep {name}".split(), check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - input=fission_env_list.stdout + input=fission_env_list.stdout, ) self.envName = name - except subprocess.CalledProcessError: # if exception raised it means that there is no appropriate namespace - logging.info(f'Creating env for {name} using image \"{image}\".') + except subprocess.CalledProcessError: + logging.info(f'Creating env for {name} using image "{image}".') self.envName = name subprocess.run( - f'fission env create --name {name} --image {image} --builder {builder}'.split(), - check=True, stdout=subprocess.DEVNULL + f"fission env create --name {name} --image {image} \ + --builder {builder}".split(), + check=True, + stdout=subprocess.DEVNULL, ) def create_function(self, name: str, env_name: str, path: str): - packageName = f'{name}-package' + packageName = f"{name}-package" try: - packages = subprocess.run('fission package list'.split(), stdout=subprocess.PIPE, check=True) + packages = subprocess.run( + "fission package list".split(), stdout=subprocess.PIPE, check=True + ) subprocess.run( - f'grep {packageName}'.split(), - check=True, - input=packages.stdout + f"grep {packageName}".split(), check=True, input=packages.stdout ) try: self.deleteFunction(name) self.deletePackage(packageName) self.createPackage(packageName, path, env_name) - functions = subprocess.run('fission function list'.split(), stdout=subprocess.PIPE, check=True) + functions = subprocess.run( + "fission function list".split(), stdout=subprocess.PIPE, check=True + ) subprocess.run( - f'grep {name}'.split(), - check=True, - input=functions.stdout - ) + f"grep {name}".split(), check=True, input=functions.stdout + ) except subprocess.CalledProcessError: self.createFunction(packageName, name) except subprocess.CalledProcessError: self.createPackage(packageName, path, env_name) self.createFunction(packageName, name) - def createPackage(self, packageName: str, path: str, envName: str) -> None: - logging.info(f'Deploying fission package...') + logging.info(f"Deploying fission package...") self.packageName = packageName - process = f'fission package create --deployarchive {path} --name {packageName} --env {envName}' + process = f"fission package create --deployarchive {path} \ + --name {packageName} --env {envName}" if self.shouldCallBuilder: - process.join(' --buildcmd ./build.sh') + process.join(" --buildcmd ./build.sh") subprocess.run(process.split(), check=True) - + def deletePackage(self, packageName: str) -> None: - logging.info(f'Deleting fission package...') - subprocess.run(f'fission package delete --name {packageName}'.split()) + logging.info(f"Deleting fission package...") + subprocess.run(f"fission package delete --name {packageName}".split()) def createFunction(self, packageName: str, name: str) -> None: - logging.info(f'Deploying fission function...') + logging.info(f"Deploying fission function...") subprocess.run( - f'fission fn create --name {name} --pkg {packageName} --entrypoint handler.handler --env {self.envName}'.split(), check=True + f"fission fn create --name {name} --pkg {packageName} \ + --entrypoint handler.handler --env {self.envName}".split(), + check=True, ) - triggerName = f'{name}-trigger' + triggerName = f"{name}-trigger" self.functionName = name self.httpTriggerName = triggerName try: - triggers = subprocess.run(f'fission httptrigger list'.split(), stdout=subprocess.PIPE, check=True) + triggers = subprocess.run( + f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True + ) subprocess.run( - f'grep {triggerName}'.split(), - check=True, - input=triggers.stdout - ) + f"grep {triggerName}".split(), check=True, input=triggers.stdout + ) except subprocess.CalledProcessError: - subprocess.run(f'fission httptrigger create --url /benchmark --method POST --name {triggerName} --function {name}'.split(), check=True) + subprocess.run( + f"fission httptrigger create --url /benchmark --method POST \ + --name {triggerName} --function {name}".split(), + check=True, + ) + + def deleteFunction(self, name: str) -> None: + logging.info(f"Deleting fission function...") + subprocess.run(f"fission fn delete --name {name}".split()) - def deleteFunction(self, name: str)-> None: - logging.info(f'Deleting fission function...') - subprocess.run(f'fission fn delete --name {name}'.split()) - def get_function(self, code_package: Benchmark) -> Function: path, size = self.package_code(code_package) @@ -316,26 +396,37 @@ def get_function(self, code_package: Benchmark) -> Function: version=code_package.language_version, ) ) - benchmark = code_package.benchmark.replace('.','-') + benchmark = code_package.benchmark.replace(".", "-") language = code_package.language_name language_runtime = code_package.language_version timeout = code_package.benchmark_config.timeout memory = code_package.benchmark_config.memory if code_package.is_cached and code_package.is_cached_valid: func_name = code_package.cached_config["name"] - code_location = os.path.join(code_package._cache_client.cache_dir, code_package._cached_config["code"]) + code_location = os.path.join( + code_package._cache_client.cache_dir, + code_package._cached_config["code"], + ) logging.info( "Using cached function {fname} in {loc}".format( fname=func_name, loc=code_location ) ) - self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) + self.create_env_if_needed( + language, + self.available_languages_images[language], + self.available_languages_builders[language], + ) self.update_function(func_name, code_package.language_name, path) return FissionFunction(func_name) elif code_package.is_cached: func_name = code_package.cached_config["name"] code_location = code_package.code_location - self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) + self.create_env_if_needed( + language, + self.available_languages_images[language], + self.available_languages_builders[language], + ) self.update_function(func_name, code_package.language_name, path) cached_cfg = code_package.cached_config cached_cfg["code_size"] = size @@ -343,7 +434,11 @@ def get_function(self, code_package: Benchmark) -> Function: cached_cfg["memory"] = memory cached_cfg["hash"] = code_package.hash self.cache_client.update_function( - self.name(), benchmark.replace('-','.'), code_package.language_name, path, cached_cfg + self.name(), + benchmark.replace("-", "."), + code_package.language_name, + path, + cached_cfg, ) code_package.query_cache() logging.info( @@ -355,11 +450,15 @@ def get_function(self, code_package: Benchmark) -> Function: else: code_location = code_package.benchmark_path func_name = "{}-{}-{}".format(benchmark, language, memory) - self.create_env_if_needed(language, self.available_languages_images[language], self.available_languages_builders[language]) + self.create_env_if_needed( + language, + self.available_languages_images[language], + self.available_languages_builders[language], + ) self.create_function(func_name, language, path) self.cache_client.add_function( deployment=self.name(), - benchmark=benchmark.replace('-','.'), + benchmark=benchmark.replace("-", "."), language=language, code_package=path, language_config={ @@ -368,13 +467,13 @@ def get_function(self, code_package: Benchmark) -> Function: "runtime": language_runtime, "memory": memory, "timeout": timeout, - "hash": code_package.hash + "hash": code_package.hash, }, storage_config={ "buckets": { "input": self.storage.input_buckets, - "output": self.storage.output_buckets - } + "output": self.storage.output_buckets, + } }, ) code_package.query_cache() diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 8fa624bb..9875c0ab 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -1,28 +1,29 @@ from sebs.faas.function import Function, ExecutionResult -import subprocess import json import datetime import requests -import os import logging + class FissionFunction(Function): def __init__(self, name: str): super().__init__(name) def sync_invoke(self, payload: dict): url = "http://localhost:5051/benchmark" - payload = json.dumps(payload) - headers = {'content-type': "application/json"} + readyPayload = json.dumps(payload) + headers = {"content-type": "application/json"} begin = datetime.datetime.now() logging.info(f"Function {self.name} invoking...") - response = requests.request("POST", url, data=payload, headers=headers) + response = requests.request("POST", url, data=readyPayload, headers=headers) end = datetime.datetime.now() - logging.info(f"Function {self.name} returned response with code: {response.status_code}") + logging.info( + f"Function {self.name} returned response with code: {response.status_code}" + ) fissionResult = ExecutionResult(begin, end) if response.status_code != 200: logging.error("Invocation of {} failed!".format(self.name)) - logging.error("Input: {}".format(payload)) + logging.error("Input: {}".format(readyPayload)) # TODO: this part is form AWS, need to be rethink # self._deployment.get_invocation_error( diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index d013b5bb..bd93d29f 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -4,6 +4,9 @@ import minio import secrets import docker +import os + + class Minio(PersistentStorage): storage_container = None @@ -14,7 +17,7 @@ class Minio(PersistentStorage): access_key = None secret_key = None port = 9000 - location = 'fissionBenchmark' + location = "fissionBenchmark" connection = None docker_client = None @@ -24,7 +27,7 @@ def __init__(self, docker_client): self.connection = self.get_connection() def start(self): - minioName = 'minio' + minioName = "minio" try: actualContainer = self.docker_client.containers.get(minioName) actualContainer.stop() @@ -34,92 +37,104 @@ def start(self): self.startMinio(minioName) def startMinio(self, minioName: str): - minioVersion = 'minio/minio:latest' + minioVersion = "minio/minio:latest" self.access_key = secrets.token_urlsafe(32) self.secret_key = secrets.token_hex(32) - logging.info('Minio container starting') - logging.info('ACCESS_KEY={}'.format(self.access_key)) - logging.info('SECRET_KEY={}'.format(self.secret_key)) + logging.info("Minio container starting") + logging.info("ACCESS_KEY={}".format(self.access_key)) + logging.info("SECRET_KEY={}".format(self.secret_key)) self.storage_container = self.docker_client.containers.run( minioVersion, - command='server /data', + command="server /data", ports={str(self.port): self.port}, environment={ - 'MINIO_ACCESS_KEY' : self.access_key, - 'MINIO_SECRET_KEY' : self.secret_key + "MINIO_ACCESS_KEY": self.access_key, + "MINIO_SECRET_KEY": self.secret_key, }, name=minioName, remove=True, - stdout=True, stderr=True, - detach=True + stdout=True, + stderr=True, + detach=True, ) self.storage_container.reload() - networks = self.storage_container.attrs['NetworkSettings']['Networks'] - self.url = '{IPAddress}:{Port}'.format( - IPAddress=networks['bridge']['IPAddress'], - Port=self.port + networks = self.storage_container.attrs["NetworkSettings"]["Networks"] + self.url = "{IPAddress}:{Port}".format( + IPAddress=networks["bridge"]["IPAddress"], Port=self.port ) - logging.info('Started minio instance at {}'.format(self.url)) + logging.info("Started minio instance at {}".format(self.url)) def get_connection(self): - return minio.Minio(self.url, - access_key=self.access_key, - secret_key=self.secret_key, - secure=False) + return minio.Minio( + self.url, + access_key=self.access_key, + secret_key=self.secret_key, + secure=False, + ) def input(self) -> List[str]: return self.input_buckets def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: input_index = self.input_index - bucket_name = '{}-{}-input'.format(name, input_index) + bucket_name = "{}-{}-input".format(name, input_index) exist = self.connection.bucket_exist(bucket_name) try: if cache: self.input_index += 1 - if exist: + if exist: return (bucket_name, input_index) else: self.connection.make_bucket(bucket_name, location=self.location) - input_buckets.append(bucket_name) + self.input_buckets.append(bucket_name) return (bucket_name, input_index) if exist: return (bucket_name, input_index) self.connection.make_bucket(bucket_name, location=self.location) return (bucket_name, input_index) - except (minio.error.BucketAlreadyOwnedByYou, minio.error.BucketAlreadyExists, minio.error.ResponseError) as err: - logging.error('Bucket creation failed!') + except ( + minio.error.BucketAlreadyOwnedByYou, + minio.error.BucketAlreadyExists, + minio.error.ResponseError, + ) as err: + logging.error("Bucket creation failed!") raise err - def add_output_bucket(self, name: str, suffix: str = "output", cache: bool = True) -> Tuple[str, int]: + def add_output_bucket( + self, name: str, suffix: str = "output", cache: bool = True + ) -> Tuple[str, int]: input_index = self.input_index - bucket_name = '{}-{}-{}'.format(name, input_index, suffix) + bucket_name = "{}-{}-{}".format(name, input_index, suffix) exist = self.connection.bucket_exist(bucket_name) try: if cache: self.input_index += 1 - if exist: + if exist: return (bucket_name, input_index) else: self.connection.make_bucket(bucket_name, location=self.location) - input_buckets.append(bucket_name) + self.input_buckets.append(bucket_name) return (bucket_name, input_index) if exist: return (bucket_name, input_index) self.connection.make_bucket(bucket_name, location=self.location) return (bucket_name, input_index) - except (minio.error.BucketAlreadyOwnedByYou, minio.error.BucketAlreadyExists, minio.error.ResponseError) as err: - logging.error('Bucket creation failed!') + except ( + minio.error.BucketAlreadyOwnedByYou, + minio.error.BucketAlreadyExists, + minio.error.ResponseError, + ) as err: + logging.error("Bucket creation failed!") raise err def output(self) -> List[str]: return self.output_buckets def download(self, bucket_name: str, key: str, filepath: str) -> None: - objects = self.connection.list_objects_v2(bucket) + objects = self.connection.list_objects_v2(bucket_name) objects = [obj.object_name for obj in objects] for obj in objects: - self.connection.fget_object(bucket, obj, os.path.join(result_dir, obj)) + self.connection.fget_object(bucket_name, obj, os.path.join(filepath, obj)) def upload(self, bucket_name: str, filepath: str, key: str): self.connection.put_object(bucket_name, filepath) From 42766022ddf2cd9352b5811a35b95af1cb88d72c Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 3 Jul 2020 01:28:04 +0200 Subject: [PATCH 28/47] add minio usage --- benchmarks/wrappers/fission/python/storage.py | 59 +++++++++++++ config/systems.json | 2 +- sebs/fission/fission.py | 85 +++++++++++++------ sebs/fission/minio.py | 38 ++++----- 4 files changed, 134 insertions(+), 50 deletions(-) create mode 100644 benchmarks/wrappers/fission/python/storage.py diff --git a/benchmarks/wrappers/fission/python/storage.py b/benchmarks/wrappers/fission/python/storage.py new file mode 100644 index 00000000..a97d2d06 --- /dev/null +++ b/benchmarks/wrappers/fission/python/storage.py @@ -0,0 +1,59 @@ +import io +import os +import uuid +import json +import sys +import minio +from flask import current_app +class storage: + instance = None + client = None + + def __init__(self): + file = open(os.path.join(os.path.dirname(__file__), "minioConfig.json"), "r") + minioConfig = json.load(file) + try: + self.client = minio.Minio( + minioConfig["url"], + access_key=minioConfig["access_key"], + secret_key=minioConfig["secret_key"], + secure=False) + except Exception as e: + current_app.logger.info(e) + + @staticmethod + def unique_name(name): + name, extension = name.split('.') + return '{name}.{random}.{extension}'.format( + name=name, + extension=extension, + random=str(uuid.uuid4()).split('-')[0] + ) + + def upload(self, bucket, file, filepath): + key_name = storage.unique_name(file) + self.client.fput_object(bucket, key_name, filepath) + return key_name + + def download(self, bucket, file, filepath): + self.client.fget_object(bucket, file, filepath) + + def download_directory(self, bucket, prefix, path): + objects = self.client.list_objects_v2(bucket, prefix, recursive=True) + for obj in objects: + file_name = obj.object_name + self.download(bucket, file_name, os.path.join(path, file_name)) + + def upload_stream(self, bucket, file, bytes_data): + key_name = storage.unique_name(file) + self.client.put_object(bucket, key_name, bytes_data, bytes_data.getbuffer().nbytes) + return key_name + + def download_stream(self, bucket, file): + data = self.client.get_object(bucket, file) + return data.read() + + def get_instance(): + if storage.instance is None: + storage.instance = storage() + return storage.instance diff --git a/config/systems.json b/config/systems.json index 8d0568dc..d3181842 100644 --- a/config/systems.json +++ b/config/systems.json @@ -109,7 +109,7 @@ "images": [], "username": "docker_user", "deployment": { - "files": ["handler.py"], + "files": ["handler.py", "storage.py"], "packages": [] } }, diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 7d004f04..970ec765 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -4,6 +4,8 @@ import subprocess import docker import stat +import json +from time import sleep from typing import Dict, Tuple, List from sebs.faas.storage import PersistentStorage from sebs.cache import Cache @@ -218,12 +220,9 @@ def shutdown(self) -> None: subprocess.run(f"fission package delete --name {self.packageName}".split()) if hasattr(self, "envName"): subprocess.run(f"fission env delete --name {self.envName}".split()) - try: - minioContainer = self.docker_client.containers.get("minio") - minioContainer.stop() - except docker.errors.NotFound: - pass + self.storage.storage_container.kill() logging.info("Minio stopped") + subprocess.run(f"minikube stop".split()) def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) @@ -240,6 +239,7 @@ def initialize(self, config: Dict[str, str] = None): Fission.install_fission_using_helm() Fission.install_fission_cli_if_needed() Fission.add_port_forwarding() + sleep(5) def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: @@ -250,7 +250,7 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: "handler.py", "requirements.txt", ".python_packages", - "build.sh", + "build.sh" ], "nodejs": ["handler.js", "package.json", "node_modules"], } @@ -258,17 +258,21 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: package_config = CONFIG_FILES[benchmark.language_name] function_dir = os.path.join(directory, "function") os.makedirs(function_dir) - if os.path.exists(os.path.join(directory, "requirements.txt")): - scriptPath = os.path.join(directory, "build.sh") - self.shouldCallBuilder = True - f = open(scriptPath, "w+") - f.write( - "pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && \ - cp -r ${SRC_PKG} ${DEPLOY_PKG}" - ) - f.close() - st = os.stat(scriptPath) - os.chmod(scriptPath, st.st_mode | stat.S_IEXEC) + minioConfig = open("./code/minioConfig.json", "w+") + minioConfigJson = {"access_key": self.storage.access_key, "secret_key": self.storage.secret_key, "url" : self.storage.url} + minioConfig.write(json.dumps(minioConfigJson)) + minioConfig.close() + reqFile = open(os.path.join(directory, "requirements.txt"),"a+") + reqFile.writelines(["minio"]) + reqFile.close() + scriptPath = os.path.join(directory, "build.sh") + self.shouldCallBuilder = True + f = open(scriptPath, "w+") + f.write( + "#!/bin/sh\npip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}" + ) + f.close() + subprocess.run(["chmod", "+x", scriptPath]) for file in os.listdir(directory): if file not in package_config: file = os.path.join(directory, file) @@ -295,7 +299,6 @@ def create_env_if_needed(self, name: str, image: str, builder: str): stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ) - subprocess.run( f"grep {name}".split(), check=True, @@ -307,12 +310,27 @@ def create_env_if_needed(self, name: str, image: str, builder: str): except subprocess.CalledProcessError: logging.info(f'Creating env for {name} using image "{image}".') self.envName = name - subprocess.run( - f"fission env create --name {name} --image {image} \ - --builder {builder}".split(), - check=True, - stdout=subprocess.DEVNULL, - ) + try: + subprocess.run( + f"fission env create --name {name} --image {image} \ + --builder {builder}".split(), + check=True, + stdout=subprocess.DEVNULL, + ) + except subprocess.CalledProcessError: + logging.info(f'Creating env {name} failed. Retrying...') + sleep(10) + try: + subprocess.run( + f"fission env create --name {name} --image {image} \ + --builder {builder}".split(), + check=True, + stdout=subprocess.DEVNULL, + ) + except subprocess.CalledProcessError: + self.storage.storage_container.kill() + logging.info("Minio stopped") + self.initialize() def create_function(self, name: str, env_name: str, path: str): packageName = f"{name}-package" @@ -342,11 +360,22 @@ def create_function(self, name: str, env_name: str, path: str): def createPackage(self, packageName: str, path: str, envName: str) -> None: logging.info(f"Deploying fission package...") self.packageName = packageName - process = f"fission package create --deployarchive {path} \ - --name {packageName} --env {envName}" - if self.shouldCallBuilder: - process.join(" --buildcmd ./build.sh") + process = f"fission package create --sourcearchive {path} \ + --name {packageName} --env {envName} --buildcmd ./build.sh" subprocess.run(process.split(), check=True) + logging.info("Waiting for package build...") + while True: + try: + packageStatus = subprocess.run(f"fission package info --name {packageName}".split(), stdout=subprocess.PIPE) + subprocess.run(f"grep succeeded".split(),check=True, input=packageStatus.stdout, stderr=subprocess.DEVNULL) + break + except subprocess.CalledProcessError: + if "failed" in packageStatus.stdout.decode("utf-8"): + logging.error("Build package failed") + raise Exception("Build package failed") + sleep(3) + continue + logging.info("Package ready") def deletePackage(self, packageName: str) -> None: logging.info(f"Deleting fission package...") diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index bd93d29f..94ccf945 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -1,6 +1,7 @@ from sebs.faas.storage import PersistentStorage from typing import List, Tuple import logging +from time import sleep import minio import secrets import docker @@ -24,19 +25,13 @@ class Minio(PersistentStorage): def __init__(self, docker_client): self.docker_client = docker_client self.start() + sleep(10) self.connection = self.get_connection() def start(self): - minioName = "minio" - try: - actualContainer = self.docker_client.containers.get(minioName) - actualContainer.stop() - actualContainer.wait() - self.startMinio(minioName) - except docker.errors.NotFound: - self.startMinio(minioName) - - def startMinio(self, minioName: str): + self.startMinio() + + def startMinio(self): minioVersion = "minio/minio:latest" self.access_key = secrets.token_urlsafe(32) self.secret_key = secrets.token_hex(32) @@ -51,7 +46,6 @@ def startMinio(self, minioName: str): "MINIO_ACCESS_KEY": self.access_key, "MINIO_SECRET_KEY": self.secret_key, }, - name=minioName, remove=True, stdout=True, stderr=True, @@ -78,7 +72,7 @@ def input(self) -> List[str]: def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: input_index = self.input_index bucket_name = "{}-{}-input".format(name, input_index) - exist = self.connection.bucket_exist(bucket_name) + exist = self.connection.bucket_exists(bucket_name) try: if cache: self.input_index += 1 @@ -91,6 +85,7 @@ def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: if exist: return (bucket_name, input_index) self.connection.make_bucket(bucket_name, location=self.location) + self.input_buckets.append(bucket_name) return (bucket_name, input_index) except ( minio.error.BucketAlreadyOwnedByYou, @@ -103,22 +98,23 @@ def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: def add_output_bucket( self, name: str, suffix: str = "output", cache: bool = True ) -> Tuple[str, int]: - input_index = self.input_index - bucket_name = "{}-{}-{}".format(name, input_index, suffix) - exist = self.connection.bucket_exist(bucket_name) + output_index = self.output_index + bucket_name = "{}-{}-{}".format(name, output_index, suffix) + exist = self.connection.bucket_exists(bucket_name) try: if cache: - self.input_index += 1 + self.output_index += 1 if exist: - return (bucket_name, input_index) + return (bucket_name, output_index) else: self.connection.make_bucket(bucket_name, location=self.location) - self.input_buckets.append(bucket_name) - return (bucket_name, input_index) + self.output_buckets.append(bucket_name) + return (bucket_name, output_index) if exist: - return (bucket_name, input_index) + return (bucket_name, output_index) self.connection.make_bucket(bucket_name, location=self.location) - return (bucket_name, input_index) + self.output_buckets.append(bucket_name) + return (bucket_name, output_index) except ( minio.error.BucketAlreadyOwnedByYou, minio.error.BucketAlreadyExists, From 25de6ea80fc6caf01227c87b2562ae93d58e2b78 Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 3 Jul 2020 01:32:34 +0200 Subject: [PATCH 29/47] lint wrappers --- benchmarks/wrappers/fission/python/handler.py | 36 +++++++++++-------- benchmarks/wrappers/fission/python/storage.py | 29 +++++++-------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index 9869f1de..2eeb5e09 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -1,6 +1,10 @@ from flask import request, jsonify, current_app -import json, datetime, os +import json +import datetime +import os + + def handler(): body = request.get_data().decode("utf-8") current_app.logger.info("Body: " + body) @@ -8,29 +12,31 @@ def handler(): current_app.logger.info("Event: " + str(event)) begin = datetime.datetime.now() from function import function + ret = function.handler(event) end = datetime.datetime.now() current_app.logger.info("Function result: " + str(ret)) - log_data = { - 'result': ret['result'] - } - if 'measurement' in ret: - log_data['measurement'] = ret['measurement'] + log_data = {"result": ret["result"]} + if "measurement" in ret: + log_data["measurement"] = ret["measurement"] results_time = (end - begin) / datetime.timedelta(microseconds=1) # cold test is_cold = False - fname = 'cold_run' + fname = "cold_run" if not os.path.exists(fname): is_cold = True - open(fname, 'a').close() + open(fname, "a").close() return jsonify( - json.dumps({ - 'begin': begin.strftime('%s.%f'), - 'end': end.strftime('%s.%f'), - 'results_time': results_time, - 'is_cold': is_cold, - 'result': log_data - })) + json.dumps( + { + "begin": begin.strftime("%s.%f"), + "end": end.strftime("%s.%f"), + "results_time": results_time, + "is_cold": is_cold, + "result": log_data, + } + ) + ) diff --git a/benchmarks/wrappers/fission/python/storage.py b/benchmarks/wrappers/fission/python/storage.py index a97d2d06..5833249e 100644 --- a/benchmarks/wrappers/fission/python/storage.py +++ b/benchmarks/wrappers/fission/python/storage.py @@ -1,10 +1,10 @@ -import io import os import uuid import json -import sys import minio from flask import current_app + + class storage: instance = None client = None @@ -14,21 +14,20 @@ def __init__(self): minioConfig = json.load(file) try: self.client = minio.Minio( - minioConfig["url"], - access_key=minioConfig["access_key"], - secret_key=minioConfig["secret_key"], - secure=False) + minioConfig["url"], + access_key=minioConfig["access_key"], + secret_key=minioConfig["secret_key"], + secure=False, + ) except Exception as e: current_app.logger.info(e) - + @staticmethod def unique_name(name): - name, extension = name.split('.') - return '{name}.{random}.{extension}'.format( - name=name, - extension=extension, - random=str(uuid.uuid4()).split('-')[0] - ) + name, extension = name.split(".") + return "{name}.{random}.{extension}".format( + name=name, extension=extension, random=str(uuid.uuid4()).split("-")[0] + ) def upload(self, bucket, file, filepath): key_name = storage.unique_name(file) @@ -46,7 +45,9 @@ def download_directory(self, bucket, prefix, path): def upload_stream(self, bucket, file, bytes_data): key_name = storage.unique_name(file) - self.client.put_object(bucket, key_name, bytes_data, bytes_data.getbuffer().nbytes) + self.client.put_object( + bucket, key_name, bytes_data, bytes_data.getbuffer().nbytes + ) return key_name def download_stream(self, bucket, file): From d6894c6c40c89ca5b8779401e7e53d95d6929476 Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 3 Jul 2020 02:00:37 +0200 Subject: [PATCH 30/47] lint fission --- sebs/fission/fission.py | 28 ++++++++++++++++++++-------- sebs/fission/minio.py | 10 +++++----- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 970ec765..c47fb3bb 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -3,7 +3,6 @@ import shutil import subprocess import docker -import stat import json from time import sleep from typing import Dict, Tuple, List @@ -250,7 +249,7 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: "handler.py", "requirements.txt", ".python_packages", - "build.sh" + "build.sh", ], "nodejs": ["handler.js", "package.json", "node_modules"], } @@ -259,17 +258,22 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: function_dir = os.path.join(directory, "function") os.makedirs(function_dir) minioConfig = open("./code/minioConfig.json", "w+") - minioConfigJson = {"access_key": self.storage.access_key, "secret_key": self.storage.secret_key, "url" : self.storage.url} + minioConfigJson = { + "access_key": self.storage.access_key, + "secret_key": self.storage.secret_key, + "url": self.storage.url, + } minioConfig.write(json.dumps(minioConfigJson)) minioConfig.close() - reqFile = open(os.path.join(directory, "requirements.txt"),"a+") + reqFile = open(os.path.join(directory, "requirements.txt"), "a+") reqFile.writelines(["minio"]) reqFile.close() scriptPath = os.path.join(directory, "build.sh") self.shouldCallBuilder = True f = open(scriptPath, "w+") f.write( - "#!/bin/sh\npip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}" + "#!/bin/sh\npip3 install -r ${SRC_PKG}/requirements.txt -t \ +${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}" ) f.close() subprocess.run(["chmod", "+x", scriptPath]) @@ -318,7 +322,7 @@ def create_env_if_needed(self, name: str, image: str, builder: str): stdout=subprocess.DEVNULL, ) except subprocess.CalledProcessError: - logging.info(f'Creating env {name} failed. Retrying...') + logging.info(f"Creating env {name} failed. Retrying...") sleep(10) try: subprocess.run( @@ -366,8 +370,16 @@ def createPackage(self, packageName: str, path: str, envName: str) -> None: logging.info("Waiting for package build...") while True: try: - packageStatus = subprocess.run(f"fission package info --name {packageName}".split(), stdout=subprocess.PIPE) - subprocess.run(f"grep succeeded".split(),check=True, input=packageStatus.stdout, stderr=subprocess.DEVNULL) + packageStatus = subprocess.run( + f"fission package info --name {packageName}".split(), + stdout=subprocess.PIPE, + ) + subprocess.run( + f"grep succeeded".split(), + check=True, + input=packageStatus.stdout, + stderr=subprocess.DEVNULL, + ) break except subprocess.CalledProcessError: if "failed" in packageStatus.stdout.decode("utf-8"): diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index 94ccf945..1bd81e62 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -1,22 +1,22 @@ +#!/usr/bin/env python3 from sebs.faas.storage import PersistentStorage from typing import List, Tuple import logging from time import sleep import minio import secrets -import docker import os class Minio(PersistentStorage): storage_container = None - input_buckets = [] - output_buckets = [] + input_buckets: List[str] = [] + output_buckets: List[str] = [] input_index = 0 output_index = 0 - access_key = None - secret_key = None + access_key: str = "" + secret_key: str = "" port = 9000 location = "fissionBenchmark" connection = None From 15922f7c06711ca1b7deef7bf9303333fa380b68 Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 3 Jul 2020 02:18:37 +0200 Subject: [PATCH 31/47] s3 comment fix --- sebs/aws/s3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sebs/aws/s3.py b/sebs/aws/s3.py index 078ffc3a..8f789d46 100644 --- a/sebs/aws/s3.py +++ b/sebs/aws/s3.py @@ -95,7 +95,7 @@ def add_output_bucket( self, name: str, suffix: str = "output", cache: bool = True ) -> Tuple[str, int]: - idx = self.request_input_buckets #are you sure? + idx = self.request_input_buckets # are you sure? name = "{}-{}-{}".format(name, idx + 1, suffix) if cache: self.request_input_buckets += 1 From 5e816bf2f88c4606e03c975ff2c497f964162d40 Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 3 Jul 2020 02:34:32 +0200 Subject: [PATCH 32/47] ignore missing types --- mypy.ini | 3 +++ sebs/fission/minio.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mypy.ini b/mypy.ini index 1be41cb6..cbc80da6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,3 +2,6 @@ [mypy-docker] ignore_missing_imports = True + +[mypy-minio] +ignore_missing_imports = True diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index 1bd81e62..3fbeff09 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from sebs.faas.storage import PersistentStorage -from typing import List, Tuple +from typing import List, Tuple, Any import logging from time import sleep import minio @@ -10,7 +10,7 @@ class Minio(PersistentStorage): - storage_container = None + storage_container: Any input_buckets: List[str] = [] output_buckets: List[str] = [] input_index = 0 @@ -19,7 +19,7 @@ class Minio(PersistentStorage): secret_key: str = "" port = 9000 location = "fissionBenchmark" - connection = None + connection: Any docker_client = None def __init__(self, docker_client): From f013a6803e11a9567729c53bd339bb36b07389d8 Mon Sep 17 00:00:00 2001 From: Jakub Pajor Date: Fri, 3 Jul 2020 02:41:29 +0200 Subject: [PATCH 33/47] [feature_fission] nodejs handler added --- benchmarks/wrappers/fission/nodejs/handler.js | 31 +++++++++++++++++++ sebs/fission/fission.py | 26 ++++++++-------- sebs/fission/fissionFunction.py | 2 +- 3 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 benchmarks/wrappers/fission/nodejs/handler.js diff --git a/benchmarks/wrappers/fission/nodejs/handler.js b/benchmarks/wrappers/fission/nodejs/handler.js new file mode 100644 index 00000000..e9453dc4 --- /dev/null +++ b/benchmarks/wrappers/fission/nodejs/handler.js @@ -0,0 +1,31 @@ +const path = require('path'), fs = require('fs'); + +exports.handler = async function(context) { + var body = JSON.stringify(context.request.body); + var unbody = JSON.parse(body); + var func = require('./function/function'); + var begin = Date.now()/1000; + var start = process.hrtime(); + var ret = await func.handler(unbody); + var elapsed = process.hrtime(start); + var end = Date.now()/1000; + var micro = elapsed[1] / 1e3 + elapsed[0] * 1e6; + var is_cold = false; + var fname = path.join('/tmp','cold_run'); + if(!fs.existsSync(fname)) { + is_cold = true; + fs.closeSync(fs.openSync(fname, 'w')); + } + + return { + status: 200, + body: JSON.stringify({ + begin, + end, + compute_time: micro, + results_time: 0, + result: ret, + is_cold + }) + }; +}; \ No newline at end of file diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index c47fb3bb..a21778d8 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -425,19 +425,19 @@ def get_function(self, code_package: Benchmark) -> Function: path, size = self.package_code(code_package) # TODO: also exception if language not in self.available_languages_images - if ( - code_package.language_version - not in self.system_config.supported_language_versions( - self.name(), code_package.language_name - ) - ): - raise Exception( - "Unsupported {language} version {version} in Fission!".format( - language=code_package.language_name, - version=code_package.language_version, - ) - ) - benchmark = code_package.benchmark.replace(".", "-") + # if ( + # code_package.language_version + # not in self.system_config.supported_language_versions( + # self.name(), code_package.language_name + # ) + # ): + # raise Exception( + # "Unsupported {language} version {version} in Fission!".format( + # language=code_package.language_name, + # version=code_package.language_version, + # ) + # ) + benchmark = code_package.benchmark.replace('.','-') language = code_package.language_name language_runtime = code_package.language_version timeout = code_package.benchmark_config.timeout diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 9875c0ab..84133e5d 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -34,7 +34,7 @@ def sync_invoke(self, payload: dict): fissionResult.stats.failure = True return fissionResult - returnContent = json.loads(json.loads(response.content)) + returnContent = json.loads(response.content) fissionResult.parse_benchmark_output(returnContent) return fissionResult From 5ad80aab6fc25fc1560aea797e6e997c2a9d7bde Mon Sep 17 00:00:00 2001 From: Jakub Pajor Date: Fri, 3 Jul 2020 03:00:44 +0200 Subject: [PATCH 34/47] [feature_fission] space after coma --- sebs/fission/fission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index a21778d8..3ffd3d74 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -437,7 +437,7 @@ def get_function(self, code_package: Benchmark) -> Function: # version=code_package.language_version, # ) # ) - benchmark = code_package.benchmark.replace('.','-') + benchmark = code_package.benchmark.replace('.', '-') language = code_package.language_name language_runtime = code_package.language_version timeout = code_package.benchmark_config.timeout From 36c03a6507ce39224ba6deb1b6d888805e3ae620 Mon Sep 17 00:00:00 2001 From: marcin Date: Fri, 3 Jul 2020 03:32:31 +0200 Subject: [PATCH 35/47] add readme --- sebs/fission/fission.py | 2 +- sebs/fission/readme.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 sebs/fission/readme.md diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 3ffd3d74..9b36b185 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -437,7 +437,7 @@ def get_function(self, code_package: Benchmark) -> Function: # version=code_package.language_version, # ) # ) - benchmark = code_package.benchmark.replace('.', '-') + benchmark = code_package.benchmark.replace(".", "-") language = code_package.language_name language_runtime = code_package.language_version timeout = code_package.benchmark_config.timeout diff --git a/sebs/fission/readme.md b/sebs/fission/readme.md new file mode 100644 index 00000000..74f4be9c --- /dev/null +++ b/sebs/fission/readme.md @@ -0,0 +1,33 @@ +# Fission + +### Requirements +- docker +- kubeclt > 1.18.4 +- miniukube > 1.11.0 +- helm > 3.2.4 + +### Run +```sh +$ python sebs.py --repetitions 1 test_invoke 010.sleep ../local/tests/ test config/fission.json +``` + +### Implemented features: +- automatic Fission instalation if nessesary +- Fission environment preparation +- config for Fission +- packaging code with support requirement file (python) +- deploying function (python, node) +- adding http trigger +- cache usage implementation +- automatic sync function invoke +- measurement executions times +- if cold start checking +- storage service usage - Minio +- wrapper-handler for Python, Node +- wrapper-storage for Python +- cleanup after benchmark + +### About +Fission has very simplified [documentation][df1]. It is difficult to find detailed information that goes beyond the deploy hello world function. Many important functionalities are omitted and others have been implemented in a very non-intuitive way (limiting the display of function logs to 20 lines). *Fission* functions as a black box, making it difficult to extract information about what is happening with the function being performed. The documentation does not explain the folder structure on which the function is deployed. *Fission* does not require configuration in the form of login data because in our case it is run locally. The user does not receive any statistics regarding the performance of the function, therefore the only certain benchmarks are time measures. *Fission* works on *Kubernetes* so it is possible to access the metrics for the given application. However, these statistics will apply to the specific pod / pods and not the function itself. In addition, these are other than those required by the statistics interface from other benchmarks (*AWS*). It is possible to obtain records from *Prometheus* existing with *Fission* in the same napespace but we were unable to obtain it therefore, the values for memory benchmarks have not been implemented. *Fission* does not provide access to storage service, which is why we implemented storage based on *MinIO*. *Fission* uses *Flask* to handle requests in *Python*. + +[df1]: From 9e1cd1c3870e1fab8fd3608f28a34038bbbc2e4d Mon Sep 17 00:00:00 2001 From: marcin Date: Tue, 7 Jul 2020 21:15:40 +0200 Subject: [PATCH 36/47] fix pr 7 comments --- config/fission.json | 3 +- config/systems.json | 14 ++- sebs/config.py | 6 ++ sebs/fission/config.py | 4 +- sebs/fission/fission.py | 170 ++++++++++++++------------------ sebs/fission/fissionFunction.py | 2 +- sebs/fission/readme.md | 10 ++ 7 files changed, 107 insertions(+), 102 deletions(-) diff --git a/config/fission.json b/config/fission.json index d28eefc1..972d7564 100644 --- a/config/fission.json +++ b/config/fission.json @@ -10,6 +10,7 @@ } }, "deployment": { - "name": "fission" + "name": "fission", + "shouldShutdown": false } } diff --git a/config/systems.json b/config/systems.json index d3181842..ff5ad886 100644 --- a/config/systems.json +++ b/config/systems.json @@ -104,23 +104,29 @@ "fission": { "languages": { "python": { - "base_images": {"3.6":""}, + "base_images": { + "env":"fission/python-env:latest", + "builder":"fission/python-builder:latest" + }, "versions": ["3.6"], "images": [], "username": "docker_user", "deployment": { "files": ["handler.py", "storage.py"], - "packages": [] + "packages": {"minio" : "^5.0.10"} } }, "nodejs": { - "base_images": {"8":""}, + "base_images": { + "env":"fission/node-env:latest", + "builder":"fission/node-builder:latest" + }, "versions": ["10.x", "12.x"], "images": [], "username": "docker_user", "deployment": { "files": ["handler.js"], - "packages": [] + "packages": {"minio" : "^7.0.16"} } } } diff --git a/sebs/config.py b/sebs/config.py index 8b0351d1..4ca5d353 100644 --- a/sebs/config.py +++ b/sebs/config.py @@ -35,3 +35,9 @@ def supported_language_versions( return self._system_config[deployment_name]["languages"][language_name][ "base_images" ].keys() + + def benchmark_base_images( + self, deployment_name: str, language_name: str + ) -> Dict[str,str]: + return self._system_config[deployment_name]["languages"][language_name]["base_images"] + diff --git a/sebs/fission/config.py b/sebs/fission/config.py index d2ff5b9d..f11b9335 100644 --- a/sebs/fission/config.py +++ b/sebs/fission/config.py @@ -19,7 +19,7 @@ def __init__(self): pass def serialize(self) -> dict: - return {"": ""} + return {} @staticmethod def initialize(config: dict, cache: Cache) -> Resources: @@ -29,9 +29,11 @@ def initialize(config: dict, cache: Cache) -> Resources: class FissionConfig(Config): name: str cache: Cache + shouldShutdown: bool def __init__(self, config: dict, cache: Cache): self.name = config["name"] + self.shouldShutdown = config["shouldShutdown"] self.cache = cache @staticmethod diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 9b36b185..f0034ee5 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -18,14 +18,8 @@ class Fission(System): - available_languages_images = { - "python": "fission/python-env:latest", - "nodejs": "fission/node-env:latest", - } - available_languages_builders = { - "python": "fission/python-builder:latest", - "nodejs": "fission/node-builder:latest", - } + language_image: str + language_builder: str storage: Minio httpTriggerName: str functionName: str @@ -209,20 +203,21 @@ def add_port_forwarding(port=5051): ) def shutdown(self) -> None: - if hasattr(self, "httpTriggerName"): - subprocess.run( - f"fission httptrigger delete --name {self.httpTriggerName}".split() - ) - if hasattr(self, "functionName"): - subprocess.run(f"fission fn delete --name {self.functionName}".split()) - if hasattr(self, "packageName"): - subprocess.run(f"fission package delete --name {self.packageName}".split()) - if hasattr(self, "envName"): - subprocess.run(f"fission env delete --name {self.envName}".split()) + if self.config.shouldShutdown: + if hasattr(self, "httpTriggerName"): + subprocess.run( + f"fission httptrigger delete --name {self.httpTriggerName}".split() + ) + if hasattr(self, "functionName"): + subprocess.run(f"fission fn delete --name {self.functionName}".split()) + if hasattr(self, "packageName"): + subprocess.run(f"fission package delete --name {self.packageName}".split()) + if hasattr(self, "envName"): + subprocess.run(f"fission env delete --name {self.envName}".split()) + subprocess.run(f"minikube stop".split()) self.storage.storage_container.kill() logging.info("Minio stopped") - subprocess.run(f"minikube stop".split()) - + def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) return self.storage @@ -265,9 +260,6 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: } minioConfig.write(json.dumps(minioConfigJson)) minioConfig.close() - reqFile = open(os.path.join(directory, "requirements.txt"), "a+") - reqFile.writelines(["minio"]) - reqFile.close() scriptPath = os.path.join(directory, "build.sh") self.shouldCallBuilder = True f = open(scriptPath, "w+") @@ -297,6 +289,7 @@ def update_function(self, name: str, env_name: str, code_path: str): self.create_function(name, env_name, code_path) def create_env_if_needed(self, name: str, image: str, builder: str): + self.envName = name try: fission_env_list = subprocess.run( "fission env list ".split(), @@ -310,10 +303,9 @@ def create_env_if_needed(self, name: str, image: str, builder: str): stderr=subprocess.DEVNULL, input=fission_env_list.stdout, ) - self.envName = name + logging.info(f"Env {name} already exist") except subprocess.CalledProcessError: logging.info(f'Creating env for {name} using image "{image}".') - self.envName = name try: subprocess.run( f"fission env create --name {name} --image {image} \ @@ -335,81 +327,81 @@ def create_env_if_needed(self, name: str, image: str, builder: str): self.storage.storage_container.kill() logging.info("Minio stopped") self.initialize() + self.create_env_if_needed(name, image, builder) def create_function(self, name: str, env_name: str, path: str): packageName = f"{name}-package" + self.createPackage(packageName, path, env_name) + self.createFunction(packageName, name) + + def createPackage(self, packageName: str, path: str, envName: str) -> None: + logging.info(f"Deploying fission package...") + self.packageName = packageName try: packages = subprocess.run( "fission package list".split(), stdout=subprocess.PIPE, check=True ) subprocess.run( - f"grep {packageName}".split(), check=True, input=packages.stdout + f"grep {packageName}".split(), check=True, input=packages.stdout, stdout=subprocess.DEVNULL ) - try: - self.deleteFunction(name) - self.deletePackage(packageName) - self.createPackage(packageName, path, env_name) - functions = subprocess.run( - "fission function list".split(), stdout=subprocess.PIPE, check=True - ) - subprocess.run( - f"grep {name}".split(), check=True, input=functions.stdout - ) - except subprocess.CalledProcessError: - self.createFunction(packageName, name) + logging.info("Package already exist") except subprocess.CalledProcessError: - self.createPackage(packageName, path, env_name) - self.createFunction(packageName, name) - - def createPackage(self, packageName: str, path: str, envName: str) -> None: - logging.info(f"Deploying fission package...") - self.packageName = packageName - process = f"fission package create --sourcearchive {path} \ - --name {packageName} --env {envName} --buildcmd ./build.sh" - subprocess.run(process.split(), check=True) - logging.info("Waiting for package build...") - while True: - try: - packageStatus = subprocess.run( - f"fission package info --name {packageName}".split(), - stdout=subprocess.PIPE, - ) - subprocess.run( - f"grep succeeded".split(), - check=True, - input=packageStatus.stdout, - stderr=subprocess.DEVNULL, - ) - break - except subprocess.CalledProcessError: - if "failed" in packageStatus.stdout.decode("utf-8"): - logging.error("Build package failed") - raise Exception("Build package failed") - sleep(3) - continue - logging.info("Package ready") + process = f"fission package create --sourcearchive {path} \ + --name {packageName} --env {envName} --buildcmd ./build.sh" + subprocess.run(process.split(), check=True) + logging.info("Waiting for package build...") + while True: + try: + packageStatus = subprocess.run( + f"fission package info --name {packageName}".split(), + stdout=subprocess.PIPE, + ) + subprocess.run( + f"grep succeeded".split(), + check=True, + input=packageStatus.stdout, + stderr=subprocess.DEVNULL, + ) + break + except subprocess.CalledProcessError: + if "failed" in packageStatus.stdout.decode("utf-8"): + logging.error("Build package failed") + raise Exception("Build package failed") + sleep(3) + continue + logging.info("Package ready") def deletePackage(self, packageName: str) -> None: logging.info(f"Deleting fission package...") subprocess.run(f"fission package delete --name {packageName}".split()) def createFunction(self, packageName: str, name: str) -> None: - logging.info(f"Deploying fission function...") - subprocess.run( - f"fission fn create --name {name} --pkg {packageName} \ - --entrypoint handler.handler --env {self.envName}".split(), - check=True, - ) triggerName = f"{name}-trigger" self.functionName = name self.httpTriggerName = triggerName + logging.info(f"Deploying fission function...") + try: + triggers = subprocess.run( + f"fission fn list".split(), stdout=subprocess.PIPE, check=True + ) + subprocess.run( + f"grep {name}".split(), check=True, input=triggers.stdout, stdout=subprocess.DEVNULL + ) + logging.info(f"Function {name} already exist") + except subprocess.CalledProcessError: + subprocess.run( + f"fission fn create --name {name} --pkg {packageName} \ + --entrypoint handler.handler --env {self.envName}".split(), + check=True, + ) try: triggers = subprocess.run( f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True ) subprocess.run( - f"grep {triggerName}".split(), check=True, input=triggers.stdout + f"grep {triggerName}".split(), check=True, input=triggers.stdout, stdout=subprocess.DEVNULL ) + logging.info(f"Trigger {triggerName} already exist") except subprocess.CalledProcessError: subprocess.run( f"fission httptrigger create --url /benchmark --method POST \ @@ -422,21 +414,9 @@ def deleteFunction(self, name: str) -> None: subprocess.run(f"fission fn delete --name {name}".split()) def get_function(self, code_package: Benchmark) -> Function: + self.language_image = self.system_config.benchmark_base_images(self.name(), code_package.language_name)["env"] + self.language_builder = self.system_config.benchmark_base_images(self.name(), code_package.language_name)["builder"] path, size = self.package_code(code_package) - - # TODO: also exception if language not in self.available_languages_images - # if ( - # code_package.language_version - # not in self.system_config.supported_language_versions( - # self.name(), code_package.language_name - # ) - # ): - # raise Exception( - # "Unsupported {language} version {version} in Fission!".format( - # language=code_package.language_name, - # version=code_package.language_version, - # ) - # ) benchmark = code_package.benchmark.replace(".", "-") language = code_package.language_name language_runtime = code_package.language_version @@ -455,8 +435,8 @@ def get_function(self, code_package: Benchmark) -> Function: ) self.create_env_if_needed( language, - self.available_languages_images[language], - self.available_languages_builders[language], + self.language_image, + self.language_builder, ) self.update_function(func_name, code_package.language_name, path) return FissionFunction(func_name) @@ -465,8 +445,8 @@ def get_function(self, code_package: Benchmark) -> Function: code_location = code_package.code_location self.create_env_if_needed( language, - self.available_languages_images[language], - self.available_languages_builders[language], + self.language_image, + self.language_builder, ) self.update_function(func_name, code_package.language_name, path) cached_cfg = code_package.cached_config @@ -493,8 +473,8 @@ def get_function(self, code_package: Benchmark) -> Function: func_name = "{}-{}-{}".format(benchmark, language, memory) self.create_env_if_needed( language, - self.available_languages_images[language], - self.available_languages_builders[language], + self.language_image, + self.language_builder, ) self.create_function(func_name, language, path) self.cache_client.add_function( diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 84133e5d..9875c0ab 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -34,7 +34,7 @@ def sync_invoke(self, payload: dict): fissionResult.stats.failure = True return fissionResult - returnContent = json.loads(response.content) + returnContent = json.loads(json.loads(response.content)) fissionResult.parse_benchmark_output(returnContent) return fissionResult diff --git a/sebs/fission/readme.md b/sebs/fission/readme.md index 74f4be9c..4482d519 100644 --- a/sebs/fission/readme.md +++ b/sebs/fission/readme.md @@ -30,4 +30,14 @@ $ python sebs.py --repetitions 1 test_invoke 010.sleep ../local/tests/ test conf ### About Fission has very simplified [documentation][df1]. It is difficult to find detailed information that goes beyond the deploy hello world function. Many important functionalities are omitted and others have been implemented in a very non-intuitive way (limiting the display of function logs to 20 lines). *Fission* functions as a black box, making it difficult to extract information about what is happening with the function being performed. The documentation does not explain the folder structure on which the function is deployed. *Fission* does not require configuration in the form of login data because in our case it is run locally. The user does not receive any statistics regarding the performance of the function, therefore the only certain benchmarks are time measures. *Fission* works on *Kubernetes* so it is possible to access the metrics for the given application. However, these statistics will apply to the specific pod / pods and not the function itself. In addition, these are other than those required by the statistics interface from other benchmarks (*AWS*). It is possible to obtain records from *Prometheus* existing with *Fission* in the same napespace but we were unable to obtain it therefore, the values for memory benchmarks have not been implemented. *Fission* does not provide access to storage service, which is why we implemented storage based on *MinIO*. *Fission* uses *Flask* to handle requests in *Python*. +### Package +If the function you want to place on fission consists of only one source file containing functions, you can create functions using this file directly. However, if our function requires additional resources (for example, requirements.txt, other file, other script) we must create a package in the form of a zip archive. To install the packages contained in the.txt requirements, the zip package must include a build script (for example, `build.sh`) that ensures that the package installation starts. The path to this file must be given when creating the package using the --buildcmd flag. + + +This is an example of the `build.sh` file using environment variables inside *Fission* namespace. + +```sh +pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG} +``` + [df1]: From f31b192315f2f0dc2e6a6d2d3adb56f660203235 Mon Sep 17 00:00:00 2001 From: marcin Date: Tue, 7 Jul 2020 21:16:29 +0200 Subject: [PATCH 37/47] linting --- sebs/config.py | 7 ++++--- sebs/fission/fission.py | 41 +++++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/sebs/config.py b/sebs/config.py index 4ca5d353..9ba8f96e 100644 --- a/sebs/config.py +++ b/sebs/config.py @@ -38,6 +38,7 @@ def supported_language_versions( def benchmark_base_images( self, deployment_name: str, language_name: str - ) -> Dict[str,str]: - return self._system_config[deployment_name]["languages"][language_name]["base_images"] - + ) -> Dict[str, str]: + return self._system_config[deployment_name]["languages"][language_name][ + "base_images" + ] diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index f0034ee5..b0be743c 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -211,13 +211,15 @@ def shutdown(self) -> None: if hasattr(self, "functionName"): subprocess.run(f"fission fn delete --name {self.functionName}".split()) if hasattr(self, "packageName"): - subprocess.run(f"fission package delete --name {self.packageName}".split()) + subprocess.run( + f"fission package delete --name {self.packageName}".split() + ) if hasattr(self, "envName"): subprocess.run(f"fission env delete --name {self.envName}".split()) subprocess.run(f"minikube stop".split()) self.storage.storage_container.kill() logging.info("Minio stopped") - + def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) return self.storage @@ -342,7 +344,10 @@ def createPackage(self, packageName: str, path: str, envName: str) -> None: "fission package list".split(), stdout=subprocess.PIPE, check=True ) subprocess.run( - f"grep {packageName}".split(), check=True, input=packages.stdout, stdout=subprocess.DEVNULL + f"grep {packageName}".split(), + check=True, + input=packages.stdout, + stdout=subprocess.DEVNULL, ) logging.info("Package already exist") except subprocess.CalledProcessError: @@ -385,7 +390,10 @@ def createFunction(self, packageName: str, name: str) -> None: f"fission fn list".split(), stdout=subprocess.PIPE, check=True ) subprocess.run( - f"grep {name}".split(), check=True, input=triggers.stdout, stdout=subprocess.DEVNULL + f"grep {name}".split(), + check=True, + input=triggers.stdout, + stdout=subprocess.DEVNULL, ) logging.info(f"Function {name} already exist") except subprocess.CalledProcessError: @@ -399,7 +407,10 @@ def createFunction(self, packageName: str, name: str) -> None: f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True ) subprocess.run( - f"grep {triggerName}".split(), check=True, input=triggers.stdout, stdout=subprocess.DEVNULL + f"grep {triggerName}".split(), + check=True, + input=triggers.stdout, + stdout=subprocess.DEVNULL, ) logging.info(f"Trigger {triggerName} already exist") except subprocess.CalledProcessError: @@ -414,8 +425,12 @@ def deleteFunction(self, name: str) -> None: subprocess.run(f"fission fn delete --name {name}".split()) def get_function(self, code_package: Benchmark) -> Function: - self.language_image = self.system_config.benchmark_base_images(self.name(), code_package.language_name)["env"] - self.language_builder = self.system_config.benchmark_base_images(self.name(), code_package.language_name)["builder"] + self.language_image = self.system_config.benchmark_base_images( + self.name(), code_package.language_name + )["env"] + self.language_builder = self.system_config.benchmark_base_images( + self.name(), code_package.language_name + )["builder"] path, size = self.package_code(code_package) benchmark = code_package.benchmark.replace(".", "-") language = code_package.language_name @@ -434,9 +449,7 @@ def get_function(self, code_package: Benchmark) -> Function: ) ) self.create_env_if_needed( - language, - self.language_image, - self.language_builder, + language, self.language_image, self.language_builder, ) self.update_function(func_name, code_package.language_name, path) return FissionFunction(func_name) @@ -444,9 +457,7 @@ def get_function(self, code_package: Benchmark) -> Function: func_name = code_package.cached_config["name"] code_location = code_package.code_location self.create_env_if_needed( - language, - self.language_image, - self.language_builder, + language, self.language_image, self.language_builder, ) self.update_function(func_name, code_package.language_name, path) cached_cfg = code_package.cached_config @@ -472,9 +483,7 @@ def get_function(self, code_package: Benchmark) -> Function: code_location = code_package.benchmark_path func_name = "{}-{}-{}".format(benchmark, language, memory) self.create_env_if_needed( - language, - self.language_image, - self.language_builder, + language, self.language_image, self.language_builder, ) self.create_function(func_name, language, path) self.cache_client.add_function( From 99339758c5cd2cae432507c90fe0fb617398b9b1 Mon Sep 17 00:00:00 2001 From: Jakub Pajor Date: Tue, 14 Jul 2020 22:20:24 +0200 Subject: [PATCH 38/47] [feature_fission] fission installation through install.py --- install.py | 40 +++++--- sebs/fission/fission.py | 154 ++----------------------------- tools/fission_preparation.py | 174 +++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 159 deletions(-) create mode 100644 tools/fission_preparation.py diff --git a/install.py b/install.py index fa7c1e43..2f6d12c8 100755 --- a/install.py +++ b/install.py @@ -4,10 +4,15 @@ import os import subprocess +from tools.fission_preparation import check_if_minikube_installed, check_if_k8s_installed, check_if_helm_installed, \ + check_if_fission_cli_installed, install_fission_cli + parser = argparse.ArgumentParser(description="Install SeBS and dependencies.") parser.add_argument("--with-pypapi", action="store_true") +parser.add_argument("--with-fission", action="store_true") args = parser.parse_args() + def execute(cmd): ret = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True @@ -18,19 +23,20 @@ def execute(cmd): ) return ret.stdout.decode("utf-8") -env_dir="sebs-virtualenv" - -print("Creating Python virtualenv at {}".format(env_dir)) -execute("python3 -mvenv {}".format(env_dir)) +# env_dir="sebs-virtualenv" +# +# print("Creating Python virtualenv at {}".format(env_dir)) +# execute("python3 -mvenv {}".format(env_dir)) +# +# print("Install Python dependencies with pip") +# execute(". {}/bin/activate && pip3 install -r requirements.txt".format(env_dir)) +# +# print("Configure mypy extensions") +# execute(". {}/bin/activate && mypy_boto3".format(env_dir)) +# +# print("Initialize git submodules") +# execute("git submodule update --init --recursive") -print("Install Python dependencies with pip") -execute(". {}/bin/activate && pip3 install -r requirements.txt".format(env_dir)) - -print("Configure mypy extensions") -execute(". {}/bin/activate && mypy_boto3".format(env_dir)) - -print("Initialize git submodules") -execute("git submodule update --init --recursive") if args.with_pypapi: print("Build and install pypapi") @@ -41,3 +47,13 @@ def execute(cmd): execute("python3 setup.py build") execute("python3 pypapi/papi_build.py") os.chdir(cur_dir) + +if args.with_fission: + check_if_minikube_installed() + check_if_k8s_installed() + check_if_helm_installed() + try: + check_if_fission_cli_installed(throws_error=True) + except subprocess.CalledProcessError: + install_fission_cli() + check_if_fission_cli_installed() diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 3ffd3d74..cfe323da 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -15,6 +15,8 @@ from sebs.benchmark import Benchmark from sebs.fission.config import FissionConfig from sebs.fission.minio import Minio +from tools.fission_preparation import check_if_minikube_installed, run_minikube, check_if_k8s_installed, \ + check_if_helm_installed, stop_minikube class Fission(System): @@ -53,146 +55,6 @@ def name(): def config(self) -> FissionConfig: return self._config - @staticmethod - def check_if_minikube_installed(): - try: - subprocess.run( - "minikube version".split(), - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError: - logging.error('ERROR: "minikube" required.') - - @staticmethod - def run_minikube(vm_driver="docker"): - try: - kube_status = subprocess.run( - "minikube status".split(), stdout=subprocess.PIPE - ) - - # if minikube is already running, - # error will be raised to prevent to be started minikube again. - subprocess.run( - "grep Stopped".split(), - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - input=kube_status.stdout, - ) - try: - logging.info("Starting minikube...") - subprocess.run( - f"minikube start --vm-driver={vm_driver}".split(), check=True - ) - except subprocess.CalledProcessError: - raise ChildProcessError - - except subprocess.CalledProcessError: - try: - subprocess.run( - "grep unusually".split(), - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - input=kube_status.stdout, - ) - logging.info("Starting minikube...") - subprocess.run( - f"minikube start --vm-driver={vm_driver}".split(), check=True - ) - except subprocess.CalledProcessError: - logging.info("Minikube already working") - pass - except ChildProcessError: - logging.error("ERROR: COULDN'T START MINIKUBE") - exit(1) - - @staticmethod - def check_if_k8s_installed(): - try: - subprocess.run( - "kubectl version".split(), - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - check=True, - ) - except subprocess.CalledProcessError: - logging.error('ERROR: "kubectl" required.') - - @staticmethod - def check_if_helm_installed(): - try: - subprocess.run( - "helm version".split(), - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError: - logging.error('ERROR: "helm" required.') - - @staticmethod - def install_fission_using_helm(k8s_namespace="fission"): - fission_url = "https://github.com/fission/fission/releases/download/1.9.0/fission-all-1.9.0.tgz" # noqa: E501 - - try: - k8s_namespaces = subprocess.run( - "kubectl get namespace".split(), - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - ) - subprocess.run( - f"grep {k8s_namespace}".split(), - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - input=k8s_namespaces.stdout, - ) - logging.info("fission namespace already exist") - except (subprocess.CalledProcessError): - logging.info( - f'No proper fission namespace.Installing Fission as "{k8s_namespace}.."' - ) - subprocess.run( - f"kubectl create namespace {k8s_namespace}".split(), check=True - ) - subprocess.run( - f"helm install --namespace {k8s_namespace} \ - --name-template fission {fission_url}".split(), - check=True, - ) - - @staticmethod - def install_fission_cli_if_needed(): - try: - subprocess.run( - ["fission"], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - shell=True, - ) - except subprocess.CalledProcessError: # fission cli is not installed - logging.info("No fission CLI - installing...") - available_os = {"darwin": "osx", "linux": "linux"} - import platform - - fission_cli_url = ( - f"https://github.com/fission/fission/releases/download/1.9.0/fission-cli-" # noqa: E501 - f"{available_os[platform.system().lower()]}" - ) - - subprocess.run( - f"curl -Lo fission {fission_cli_url} \ - && chmod +x fission \ - && sudo mv fission /usr/local/bin/", - stdout=subprocess.DEVNULL, - check=True, - shell=True, - ) - @staticmethod def add_port_forwarding(port=5051): podName = ( @@ -221,7 +83,7 @@ def shutdown(self) -> None: subprocess.run(f"fission env delete --name {self.envName}".split()) self.storage.storage_container.kill() logging.info("Minio stopped") - subprocess.run(f"minikube stop".split()) + stop_minikube() def get_storage(self, replace_existing: bool = False) -> PersistentStorage: self.storage = Minio(self.docker_client) @@ -231,12 +93,10 @@ def initialize(self, config: Dict[str, str] = None): if config is None: config = {} - Fission.check_if_minikube_installed() - Fission.run_minikube() - Fission.check_if_k8s_installed() - Fission.check_if_helm_installed() - Fission.install_fission_using_helm() - Fission.install_fission_cli_if_needed() + check_if_minikube_installed() + check_if_k8s_installed() + check_if_helm_installed() + run_minikube() Fission.add_port_forwarding() sleep(5) diff --git a/tools/fission_preparation.py b/tools/fission_preparation.py new file mode 100644 index 00000000..a6794325 --- /dev/null +++ b/tools/fission_preparation.py @@ -0,0 +1,174 @@ +import subprocess + + +def run_minikube(vm_driver="docker"): + try: + kube_status = subprocess.run( + "minikube status".split(), stdout=subprocess.PIPE + ) + + # if minikube is already running, + # error will be raised to prevent to be started minikube again. + subprocess.run( + "grep Stopped".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=kube_status.stdout, + ) + try: + print("Starting minikube...") + subprocess.run( + f"minikube start --vm-driver={vm_driver}".split(), check=True + ) + except subprocess.CalledProcessError: + raise ChildProcessError + + except subprocess.CalledProcessError: + try: + subprocess.run( + "grep unusually".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=kube_status.stdout, + ) + print("Starting minikube...") + subprocess.run( + f"minikube start --vm-driver={vm_driver}".split(), check=True + ) + except subprocess.CalledProcessError: + print("Minikube already working") + pass + except ChildProcessError: + print("ERROR: COULDN'T START MINIKUBE") + exit(1) + + +def install_fission_using_helm(k8s_namespace="fission"): + fission_url = "https://github.com/fission/fission/releases/download/1.9.0/fission-all-1.9.0.tgz" # noqa: E501 + + try: + k8s_namespaces = subprocess.run( + "kubectl get namespace".split(), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + f"grep {k8s_namespace}".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=k8s_namespaces.stdout, + ) + print("fission namespace already exist") + except (subprocess.CalledProcessError): + print( + f'No proper fission namespace.Installing Fission as "{k8s_namespace}.."' + ) + subprocess.run( + f"kubectl create namespace {k8s_namespace}".split(), check=True + ) + subprocess.run( + f"helm install --namespace {k8s_namespace} \ + --name-template fission {fission_url}".split(), + check=True, + ) + + +def install_fission_cli(): + try: + subprocess.run( + ["fission"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + shell=True, + ) + except subprocess.CalledProcessError: # fission cli is not installed + print("No fission CLI - installing...") + available_os = {"darwin": "osx", "linux": "linux"} + import platform + + fission_cli_url = ( + f"https://github.com/fission/fission/releases/download/1.9.0/fission-cli-" # noqa: E501 + f"{available_os[platform.system().lower()]}" + ) + + subprocess.run( + f"curl -Lo fission {fission_cli_url} \ + && chmod +x fission \ + && sudo mv fission /usr/local/bin/", + stdout=subprocess.DEVNULL, + check=True, + shell=True, + ) + + +def check_if_k8s_installed(): + try: + subprocess.run( + "kubectl".split(), + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) + except subprocess.CalledProcessError: + print('ERROR: "kubectl" required.') + exit(1) + + +def check_if_fission_cli_installed(throws_error=False): + try: + subprocess.run( + ["fission"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + shell=True, + ) + except subprocess.CalledProcessError: # fission cli is not installed + if not throws_error: + print('ERROR: "fission" not installed or installed incorrectly.') + exit(1) + else: + raise subprocess.CalledProcessError + + +def check_if_helm_installed(): + try: + subprocess.run( + "helm version".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except subprocess.CalledProcessError: + print('ERROR: "helm" required.') + exit(1) + + +def check_if_minikube_installed(): + try: + subprocess.run( + "minikube version".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except subprocess.CalledProcessError: + print('ERROR: "minikube" required.') + exit(1) + + +def stop_minikube(): + try: + subprocess.run( + "minikube stop".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except subprocess.CalledProcessError: + print('ERROR: couldn\'t stop minikube.') + exit(1) From 4a4403b33aeccf511a962891225643bdc3d13fdc Mon Sep 17 00:00:00 2001 From: Jakub Pajor Date: Tue, 14 Jul 2020 22:48:21 +0200 Subject: [PATCH 39/47] [feature_fission] minio storage added to fission nodejs --- benchmarks/wrappers/fission/nodejs/storage.js | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 benchmarks/wrappers/fission/nodejs/storage.js diff --git a/benchmarks/wrappers/fission/nodejs/storage.js b/benchmarks/wrappers/fission/nodejs/storage.js new file mode 100644 index 00000000..b3d96f8d --- /dev/null +++ b/benchmarks/wrappers/fission/nodejs/storage.js @@ -0,0 +1,63 @@ + +const minio = require('minio'), + uuid = require('uuid'), + util = require('util'), + stream = require('stream'), + fs = require('fs'); + +class minio_storage { + + constructor() { + let minioConfig = JSON.parse(fs.readFileSync('minioConfig.json')); + let address = minioConfig["url"]; + let access_key = minioConfig["access_key"]; + let secret_key = minioConfig["secret_key"]; + + this.client = new minio.Client( + { + endPoint: address.split(':')[0], + port: parseInt(address.split(':')[1], 10), + accessKey: access_key, + secretKey: secret_key, + useSSL: false + } + ); + } + + unique_name(file) { + let [name, extension] = file.split('.'); + let uuid_name = uuid.v4().split('-')[0]; + return util.format('%s.%s.%s', name, uuid_name, extension); + } + + upload(bucket, file, filepath) { + let uniqueName = this.unique_name(file); + return [uniqueName, this.client.fPutObject(bucket, uniqueName, filepath)]; + }; + + download(bucket, file, filepath) { + return this.client.fGetObject(bucket, file, filepath); + }; + + uploadStream(bucket, file) { + var write_stream = new stream.PassThrough(); + let uniqueName = this.unique_name(file); + let promise = this.client.putObject(bucket, uniqueName, write_stream, write_stream.size); + return [write_stream, promise, uniqueName]; + }; + + downloadStream(bucket, file) { + var read_stream = new stream.PassThrough(); + return this.client.getObject(bucket, file); + }; + + static get_instance() { + if(!this.instance) { + this.instance = new storage(); + } + return this.instance; + } + + +}; +exports.storage = minio_storage; From d732d64737b9bfe0653455e502a61810cec63ff1 Mon Sep 17 00:00:00 2001 From: Marcin Copik Date: Fri, 14 Jun 2024 18:34:48 +0200 Subject: [PATCH 40/47] [fission] Linting --- sebs/fission/fission.py | 37 ++++++++++++++++++--------------- sebs/fission/fissionFunction.py | 4 +--- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 25b7fa08..93b29cee 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -15,8 +15,13 @@ from sebs.benchmark import Benchmark from sebs.fission.config import FissionConfig from sebs.fission.minio import Minio -from tools.fission_preparation import check_if_minikube_installed, run_minikube, check_if_k8s_installed, \ - check_if_helm_installed, stop_minikube +from tools.fission_preparation import ( + check_if_minikube_installed, + run_minikube, + check_if_k8s_installed, + check_if_helm_installed, + stop_minikube, +) class Fission(System): @@ -67,15 +72,11 @@ def add_port_forwarding(port=5051): def shutdown(self) -> None: if self.config.shouldShutdown: if hasattr(self, "httpTriggerName"): - subprocess.run( - f"fission httptrigger delete --name {self.httpTriggerName}".split() - ) + subprocess.run(f"fission httptrigger delete --name {self.httpTriggerName}".split()) if hasattr(self, "functionName"): subprocess.run(f"fission fn delete --name {self.functionName}".split()) if hasattr(self, "packageName"): - subprocess.run( - f"fission package delete --name {self.packageName}".split() - ) + subprocess.run(f"fission package delete --name {self.packageName}".split()) if hasattr(self, "envName"): subprocess.run(f"fission env delete --name {self.envName}".split()) stop_minikube() @@ -140,9 +141,7 @@ def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: "zip -r {}.zip ./".format(benchmark.benchmark).split(), stdout=subprocess.DEVNULL, ) - benchmark_archive = "{}.zip".format( - os.path.join(directory, benchmark.benchmark) - ) + benchmark_archive = "{}.zip".format(os.path.join(directory, benchmark.benchmark)) logging.info("Created {} archive".format(benchmark_archive)) bytes_size = os.path.getsize(benchmark_archive) return benchmark_archive, bytes_size @@ -304,12 +303,12 @@ def get_function(self, code_package: Benchmark) -> Function: code_package._cached_config["code"], ) logging.info( - "Using cached function {fname} in {loc}".format( - fname=func_name, loc=code_location - ) + "Using cached function {fname} in {loc}".format(fname=func_name, loc=code_location) ) self.create_env_if_needed( - language, self.language_image, self.language_builder, + language, + self.language_image, + self.language_builder, ) self.update_function(func_name, code_package.language_name, path) return FissionFunction(func_name) @@ -317,7 +316,9 @@ def get_function(self, code_package: Benchmark) -> Function: func_name = code_package.cached_config["name"] code_location = code_package.code_location self.create_env_if_needed( - language, self.language_image, self.language_builder, + language, + self.language_image, + self.language_builder, ) self.update_function(func_name, code_package.language_name, path) cached_cfg = code_package.cached_config @@ -343,7 +344,9 @@ def get_function(self, code_package: Benchmark) -> Function: code_location = code_package.benchmark_path func_name = "{}-{}-{}".format(benchmark, language, memory) self.create_env_if_needed( - language, self.language_image, self.language_builder, + language, + self.language_image, + self.language_builder, ) self.create_function(func_name, language, path) self.cache_client.add_function( diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index 9875c0ab..f44c62a0 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -17,9 +17,7 @@ def sync_invoke(self, payload: dict): logging.info(f"Function {self.name} invoking...") response = requests.request("POST", url, data=readyPayload, headers=headers) end = datetime.datetime.now() - logging.info( - f"Function {self.name} returned response with code: {response.status_code}" - ) + logging.info(f"Function {self.name} returned response with code: {response.status_code}") fissionResult = ExecutionResult(begin, end) if response.status_code != 200: logging.error("Invocation of {} failed!".format(self.name)) From c0ffa553bf66172d7c3572d82eefa8c944810c4a Mon Sep 17 00:00:00 2001 From: prajinkhadka Date: Wed, 17 Jul 2024 21:58:31 -0500 Subject: [PATCH 41/47] WORKING --- .../110.dynamic-html/python/function.py | 3 + .../wrappers/fission/python/__main__.py | 51 + benchmarks/wrappers/fission/python/build.sh | 2 + benchmarks/wrappers/fission/python/handler.py | 84 +- benchmarks/wrappers/fission/python/setup.py | 14 + benchmarks/wrappers/fission/python/storage.py | 112 +- config/example.json | 33 +- config/systems.json | 23 +- dockerfiles/fission/python/Dockerfile.build | 26 + .../fission/python/Dockerfile.function | 6 + out_storage.json | 10 + sebs.py | 2 +- sebs/aws/aws.py | 2 +- sebs/benchmark.py | 7 + sebs/faas/config.py | 8 + sebs/faas/system.py | 3 + sebs/fission/config.py | 302 +++- sebs/fission/fission.py | 1235 +++++++++++++---- sebs/fission/function.py | 71 + sebs/fission/storage.py | 28 + sebs/fission/triggers.py | 111 ++ sebs/sebs.py | 7 + sebs/storage/minio.py | 1 + tools/build_docker_images.py | 3 +- 24 files changed, 1759 insertions(+), 385 deletions(-) create mode 100644 benchmarks/wrappers/fission/python/__main__.py create mode 100644 benchmarks/wrappers/fission/python/build.sh create mode 100644 benchmarks/wrappers/fission/python/setup.py create mode 100644 dockerfiles/fission/python/Dockerfile.build create mode 100644 dockerfiles/fission/python/Dockerfile.function create mode 100644 out_storage.json create mode 100644 sebs/fission/function.py create mode 100644 sebs/fission/storage.py create mode 100644 sebs/fission/triggers.py diff --git a/benchmarks/100.webapps/110.dynamic-html/python/function.py b/benchmarks/100.webapps/110.dynamic-html/python/function.py index 7c990f4e..7e9d03df 100644 --- a/benchmarks/100.webapps/110.dynamic-html/python/function.py +++ b/benchmarks/100.webapps/110.dynamic-html/python/function.py @@ -8,8 +8,11 @@ SCRIPT_DIR = path.abspath(path.join(path.dirname(__file__))) + +from flask import request def handler(event): + event = request.get_json(force=True) # start timing name = event.get('username') size = event.get('random_len') diff --git a/benchmarks/wrappers/fission/python/__main__.py b/benchmarks/wrappers/fission/python/__main__.py new file mode 100644 index 00000000..21ead0d9 --- /dev/null +++ b/benchmarks/wrappers/fission/python/__main__.py @@ -0,0 +1,51 @@ +import logging +import datetime +import os + +import minio + +def main(args): + logging.getLogger().setLevel(logging.INFO) + begin = datetime.datetime.now() + args['request-id'] = os.getenv('__OW_ACTIVATION_ID') + args['income-timestamp'] = begin.timestamp() + + for arg in ["MINIO_STORAGE_CONNECTION_URL", "MINIO_STORAGE_ACCESS_KEY", "MINIO_STORAGE_SECRET_KEY"]: + os.environ[arg] = args[arg] + del args[arg] + + try: + from function import function + ret = function.handler(args) + end = datetime.datetime.now() + logging.info("Function result: {}".format(ret)) + log_data = {"result": ret["result"]} + if "measurement" in ret: + log_data["measurement"] = ret["measurement"] + + results_time = (end - begin) / datetime.timedelta(microseconds=1) + + is_cold = False + fname = "cold_run" + if not os.path.exists(fname): + is_cold = True + open(fname, "a").close() + + return { + "begin": begin.strftime("%s.%f"), + "end": end.strftime("%s.%f"), + "request_id": os.getenv('__OW_ACTIVATION_ID'), + "results_time": results_time, + "is_cold": is_cold, + "result": log_data, + } + except Exception as e: + end = datetime.datetime.now() + results_time = (end - begin) / datetime.timedelta(microseconds=1) + return { + "begin": begin.strftime("%s.%f"), + "end": end.strftime("%s.%f"), + "request_id": os.getenv('__OW_ACTIVATION_ID'), + "results_time": results_time, + "result": f"Error - invocation failed! Reason: {e}" + } diff --git a/benchmarks/wrappers/fission/python/build.sh b/benchmarks/wrappers/fission/python/build.sh new file mode 100644 index 00000000..c9e6acd5 --- /dev/null +++ b/benchmarks/wrappers/fission/python/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG} diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index 2eeb5e09..d79e3b07 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -1,42 +1,42 @@ -from flask import request, jsonify, current_app - -import json -import datetime -import os - - -def handler(): - body = request.get_data().decode("utf-8") - current_app.logger.info("Body: " + body) - event = json.loads(body) - current_app.logger.info("Event: " + str(event)) - begin = datetime.datetime.now() - from function import function - - ret = function.handler(event) - end = datetime.datetime.now() - current_app.logger.info("Function result: " + str(ret)) - log_data = {"result": ret["result"]} - if "measurement" in ret: - log_data["measurement"] = ret["measurement"] - - results_time = (end - begin) / datetime.timedelta(microseconds=1) - - # cold test - is_cold = False - fname = "cold_run" - if not os.path.exists(fname): - is_cold = True - open(fname, "a").close() - - return jsonify( - json.dumps( - { - "begin": begin.strftime("%s.%f"), - "end": end.strftime("%s.%f"), - "results_time": results_time, - "is_cold": is_cold, - "result": log_data, - } - ) - ) +# from flask import request, jsonify, current_app +# +# import json +# import datetime +# import os +# +# +# def handler(): +# body = request.get_data().decode("utf-8") +# current_app.logger.info("Body: " + body) +# event = json.loads(body) +# current_app.logger.info("Event: " + str(event)) +# begin = datetime.datetime.now() +# from function import function +# +# ret = function.handler(event) +# end = datetime.datetime.now() +# current_app.logger.info("Function result: " + str(ret)) +# log_data = {"result": ret["result"]} +# if "measurement" in ret: +# log_data["measurement"] = ret["measurement"] +# +# results_time = (end - begin) / datetime.timedelta(microseconds=1) +# +# # cold test +# is_cold = False +# fname = "cold_run" +# if not os.path.exists(fname): +# is_cold = True +# open(fname, "a").close() +# +# return jsonify( +# json.dumps( +# { +# "begin": begin.strftime("%s.%f"), +# "end": end.strftime("%s.%f"), +# "results_time": results_time, +# "is_cold": is_cold, +# "result": log_data, +# } +# ) +# ) diff --git a/benchmarks/wrappers/fission/python/setup.py b/benchmarks/wrappers/fission/python/setup.py new file mode 100644 index 00000000..229fd1e7 --- /dev/null +++ b/benchmarks/wrappers/fission/python/setup.py @@ -0,0 +1,14 @@ +from distutils.core import setup +from glob import glob +from pkg_resources import parse_requirements + +with open('requirements.txt') as f: + requirements = [str(r) for r in parse_requirements(f)] + +setup( + name='function', + install_requires=requirements, + packages=['function'], + package_dir={'function': '.'}, + package_data={'function': glob('**', recursive=True)}, +) diff --git a/benchmarks/wrappers/fission/python/storage.py b/benchmarks/wrappers/fission/python/storage.py index 5833249e..1d15ff61 100644 --- a/benchmarks/wrappers/fission/python/storage.py +++ b/benchmarks/wrappers/fission/python/storage.py @@ -2,7 +2,7 @@ import uuid import json import minio -from flask import current_app +import logging class storage: @@ -10,24 +10,43 @@ class storage: client = None def __init__(self): - file = open(os.path.join(os.path.dirname(__file__), "minioConfig.json"), "r") - minioConfig = json.load(file) try: + """ + Minio does not allow another way of configuring timeout for connection. + The rest of configuration is copied from source code of Minio. + """ + import urllib3 + from datetime import timedelta + + timeout = timedelta(seconds=1).seconds + + mgr = urllib3.PoolManager( + timeout=urllib3.util.Timeout(connect=timeout, read=timeout), + maxsize=10, + retries=urllib3.Retry( + total=5, backoff_factor=0.2, status_forcelist=[500, 502, 503, 504] + ) + ) self.client = minio.Minio( - minioConfig["url"], - access_key=minioConfig["access_key"], - secret_key=minioConfig["secret_key"], + os.getenv("MINIO_STORAGE_CONNECTION_URL"), + access_key=os.getenv("MINIO_STORAGE_ACCESS_KEY"), + secret_key=os.getenv("MINIO_STORAGE_SECRET_KEY"), secure=False, + http_client=mgr ) except Exception as e: - current_app.logger.info(e) + logging.info(e) + raise e @staticmethod def unique_name(name): - name, extension = name.split(".") - return "{name}.{random}.{extension}".format( - name=name, extension=extension, random=str(uuid.uuid4()).split("-")[0] - ) + name, extension = os.path.splitext(name) + return '{name}.{random}{extension}'.format( + name=name, + extension=extension, + random=str(uuid.uuid4()).split('-')[0] + ) + def upload(self, bucket, file, filepath): key_name = storage.unique_name(file) @@ -38,7 +57,7 @@ def download(self, bucket, file, filepath): self.client.fget_object(bucket, file, filepath) def download_directory(self, bucket, prefix, path): - objects = self.client.list_objects_v2(bucket, prefix, recursive=True) + objects = self.client.list_objects(bucket, prefix, recursive=True) for obj in objects: file_name = obj.object_name self.download(bucket, file_name, os.path.join(path, file_name)) @@ -54,7 +73,76 @@ def download_stream(self, bucket, file): data = self.client.get_object(bucket, file) return data.read() + @staticmethod def get_instance(): if storage.instance is None: storage.instance = storage() return storage.instance + + + + + + + + +# import os +# import uuid +# import json +# import minio +# from flask import current_app +# +# +# class storage: +# instance = None +# client = None +# +# def __init__(self): +# file = open(os.path.join(os.path.dirname(__file__), "minioConfig.json"), "r") +# minioConfig = json.load(file) +# try: +# self.client = minio.Minio( +# minioConfig["url"], +# access_key=minioConfig["access_key"], +# secret_key=minioConfig["secret_key"], +# secure=False, +# ) +# except Exception as e: +# current_app.logger.info(e) +# +# @staticmethod +# def unique_name(name): +# name, extension = name.split(".") +# return "{name}.{random}.{extension}".format( +# name=name, extension=extension, random=str(uuid.uuid4()).split("-")[0] +# ) +# +# def upload(self, bucket, file, filepath): +# key_name = storage.unique_name(file) +# self.client.fput_object(bucket, key_name, filepath) +# return key_name +# +# def download(self, bucket, file, filepath): +# self.client.fget_object(bucket, file, filepath) +# +# def download_directory(self, bucket, prefix, path): +# objects = self.client.list_objects_v2(bucket, prefix, recursive=True) +# for obj in objects: +# file_name = obj.object_name +# self.download(bucket, file_name, os.path.join(path, file_name)) +# +# def upload_stream(self, bucket, file, bytes_data): +# key_name = storage.unique_name(file) +# self.client.put_object( +# bucket, key_name, bytes_data, bytes_data.getbuffer().nbytes +# ) +# return key_name +# +# def download_stream(self, bucket, file): +# data = self.client.get_object(bucket, file) +# return data.read() +# +# def get_instance(): +# if storage.instance is None: +# storage.instance = storage() +# return storage.instance diff --git a/config/example.json b/config/example.json index dc4da9ad..bf5a456c 100644 --- a/config/example.json +++ b/config/example.json @@ -6,7 +6,7 @@ "download_results": false, "runtime": { "language": "python", - "version": "3.7" + "version": "3.8" }, "type": "invocation-overhead", "perf-cost": { @@ -78,11 +78,32 @@ "password": "" }, "storage": { - "address": "", - "mapped_port": -1, - "access_key": "", - "secret_key": "", - "instance_id": "", + "address": "localhost:9011", + "mapped_port": 9011, + "access_key": "ioyzpSAmJWjpdgL_0f_HkKa-wIBfdiPrUyGaHDzk3dg", + "secret_key": "f68742dfcc91132ab01601271587736173a8df8daa6118c327bfdd28a1f181d3", + "instance_id": "338e8f4d9e51ea6f6e7a9683f80b0a7c1f0f740f93b247eefd6185fdc0c1fb3e", + "input_buckets": [], + "output_buckets": [], + "type": "minio" + } + }, + "fission": { + "shutdownStorage": false, + "removeCluster": false, + "fissionExec": "fission", + "docker_registry": { + "registry": "", + "username": "", + "password": "" + }, + + "storage": { + "address": "localhost:9011", + "mapped_port": 9011, + "access_key": "kBc9m5lA7Knej-ZDf9I6KRgm9mBAendq3AvriBvDEEI", + "secret_key": "c475e70264063e366f74a63fae338c0ed51eccca2a1d2c79b43945eaa242d355", + "instance_id": "2d92fb4163da76e806bf3969038ee1223562dd2e5ba845dd62b74ed6a0aca8b3", "input_buckets": [], "output_buckets": [], "type": "minio" diff --git a/config/systems.json b/config/systems.json index 7bc9700a..d81d095d 100644 --- a/config/systems.json +++ b/config/systems.json @@ -239,27 +239,30 @@ "languages": { "python": { "base_images": { - "env":"fission/python-env:latest", - "builder":"fission/python-builder:latest" + "3.7":"fission/python-builder-3.7", + "3.8":"fission/python-builder-3.8", + "3.9":"fission/python-builder-3.9", + "3.10":"fission/python-builder-3.10" }, - "versions": ["3.6"], - "images": [], + "versions": ["3.7", "3.8", "3.9", "3.10"], + "images": ["build"], "username": "docker_user", "deployment": { - "files": ["handler.py", "storage.py"], + "files": ["handler.py", "storage.py", "setup.py", "__main__.py", "build.sh"], "packages": {"minio" : "^5.0.10"} } }, "nodejs": { "base_images": { - "env":"fission/node-env:latest", - "builder":"fission/node-builder:latest" + "12":"fission/node-builder-12", + "14":"fission/node-builder-14", + "16":"fission/node-builder-16" }, - "versions": ["10.x", "12.x"], - "images": [], + "versions": ["12", "14", "16"], + "images": ["build"], "username": "docker_user", "deployment": { - "files": ["handler.js"], + "files": ["index.js", "storage.js"], "packages": {"minio" : "^7.0.16"} } } diff --git a/dockerfiles/fission/python/Dockerfile.build b/dockerfiles/fission/python/Dockerfile.build new file mode 100644 index 00000000..a7005779 --- /dev/null +++ b/dockerfiles/fission/python/Dockerfile.build @@ -0,0 +1,26 @@ +ARG BASE_IMAGE +FROM ${BASE_IMAGE} +ARG VERSION +ENV PYTHON_VERSION=${VERSION} + + +# useradd, groupmod +# RUN apt install -y shadow-utils zip +# RUN apt update -y && yum install -y libffi-dev +RUN apk add --no-cache shadow zip libffi-dev gcc musl-dev make curl +# RUN apk add --no-cache shadow zip libffi-dev curl +ENV GOSU_VERSION 1.14 +# https://github.com/tianon/gosu/releases/tag/1.14 +# key https://keys.openpgp.org/search?q=tianon%40debian.org +RUN curl -o /usr/local/bin/gosu -SL "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64" \ + && chmod +x /usr/local/bin/gosu +RUN mkdir -p /sebs/ +COPY dockerfiles/python_installer.sh /sebs/installer.sh +COPY dockerfiles/entrypoint.sh /sebs/entrypoint.sh +RUN chmod +x /sebs/entrypoint.sh + +# useradd and groupmod is installed in /usr/sbin which is not in PATH +ENV PATH=/usr/sbin:$PATH +ENV SCRIPT_FILE=/mnt/function/package.sh +CMD /bin/bash /sebs/installer.sh +ENTRYPOINT ["/sebs/entrypoint.sh"] diff --git a/dockerfiles/fission/python/Dockerfile.function b/dockerfiles/fission/python/Dockerfile.function new file mode 100644 index 00000000..db2edbad --- /dev/null +++ b/dockerfiles/fission/python/Dockerfile.function @@ -0,0 +1,6 @@ +ARG BASE_IMAGE +FROM $BASE_IMAGE +ARG VERSION +ENV PYTHON_VERSION=${VERSION} +# COPY . function/ + diff --git a/out_storage.json b/out_storage.json new file mode 100644 index 00000000..13df4926 --- /dev/null +++ b/out_storage.json @@ -0,0 +1,10 @@ +{ + "address": "localhost:9011", + "mapped_port": 9011, + "access_key": "kBc9m5lA7Knej-ZDf9I6KRgm9mBAendq3AvriBvDEEI", + "secret_key": "c475e70264063e366f74a63fae338c0ed51eccca2a1d2c79b43945eaa242d355", + "instance_id": "2d92fb4163da76e806bf3969038ee1223562dd2e5ba845dd62b74ed6a0aca8b3", + "output_buckets": [], + "input_buckets": [], + "type": "minio" +} \ No newline at end of file diff --git a/sebs.py b/sebs.py index ff7f7769..4df5352d 100755 --- a/sebs.py +++ b/sebs.py @@ -88,7 +88,7 @@ def common_params(func): @click.option( "--deployment", default=None, - type=click.Choice(["azure", "aws", "gcp", "local", "openwhisk"]), + type=click.Choice(["azure", "aws", "gcp", "local", "openwhisk", "fission"]), help="Cloud deployment to use.", ) @click.option( diff --git a/sebs/aws/aws.py b/sebs/aws/aws.py index 6dc70e52..84cb1d4c 100644 --- a/sebs/aws/aws.py +++ b/sebs/aws/aws.py @@ -156,7 +156,7 @@ def package_code( bytes_size = os.path.getsize(os.path.join(directory, benchmark_archive)) mbytes = bytes_size / 1024.0 / 1024.0 self.logging.info("Zip archive size {:2f} MB".format(mbytes)) - + exit(0) return os.path.join(directory, "{}.zip".format(benchmark)), bytes_size def _map_language_runtime(self, language: str, runtime: str): diff --git a/sebs/benchmark.py b/sebs/benchmark.py index 90eed6ae..2804a4b5 100644 --- a/sebs/benchmark.py +++ b/sebs/benchmark.py @@ -342,12 +342,19 @@ def install_dependencies(self, output_dir): ).format(deployment=self._deployment_name, language=self.language_name) ) else: + print("are we in the else Block") repo_name = self._system_config.docker_repository() + print("THE REPO NAME IS", repo_name) + print("The deployment name we get here is", self._deployment_name) image_name = "build.{deployment}.{language}.{runtime}".format( deployment=self._deployment_name, language=self.language_name, runtime=self.language_version, ) + # PK: To do: Marcin Need to add this docker image with fission to the dockerhub + # image_name = image_name.replace('fission', 'aws') + print("THE Iage NAME IS", image_name) + print("THE repo name ", repo_name) try: self._docker_client.images.get(repo_name + ":" + image_name) except docker.errors.ImageNotFound: diff --git a/sebs/faas/config.py b/sebs/faas/config.py index 19c7d3ab..81dc6018 100644 --- a/sebs/faas/config.py +++ b/sebs/faas/config.py @@ -204,7 +204,15 @@ def deserialize(config: dict, cache: Cache, handlers: LoggingHandlers) -> Config from sebs.openwhisk.config import OpenWhiskConfig implementations["openwhisk"] = OpenWhiskConfig.deserialize + + if has_platform("fission"): + from sebs.fission.config import FissionConfig + + implementations["fission"] = FissionConfig.deserialize + + print("THe implementations are", implementations) func = implementations.get(name) + print("The func is", func) assert func, "Unknown config type!" return func(config[name] if name in config else config, cache, handlers) diff --git a/sebs/faas/system.py b/sebs/faas/system.py index 2576a0ef..88b553f9 100644 --- a/sebs/faas/system.py +++ b/sebs/faas/system.py @@ -227,6 +227,9 @@ def get_function(self, code_package: Benchmark, func_name: Optional[str] = None) else "function {} not found in cache.".format(func_name) ) self.logging.info("Creating new function! Reason: " + msg) + # PK: + print("CREATING NEW FUNCUIN FOR FISSION", func_name) + print("SELF>CREATE FUNC", self.create_function) function = self.create_function(code_package, func_name) self.cache_client.add_function( deployment_name=self.name(), diff --git a/sebs/fission/config.py b/sebs/fission/config.py index f11b9335..431f0c7b 100644 --- a/sebs/fission/config.py +++ b/sebs/fission/config.py @@ -1,29 +1,256 @@ +import subprocess +from time import sleep + from sebs.faas.config import Config, Credentials, Resources from sebs.cache import Cache +from sebs.utils import LoggingHandlers +from sebs.storage.config import MinioConfig -class FissionCredentials(Credentials): - def __init__(self): - pass +from typing import cast, Optional +class FissionCredentials(Credentials): @staticmethod - def initialize(config: dict, cache: Cache) -> Credentials: + def deserialize(config: dict, cache: Cache, handlers: LoggingHandlers) -> Credentials: return FissionCredentials() def serialize(self) -> dict: - pass + return {} class FissionResources(Resources): - def __init__(self): - pass + def __init__( + self, + registry: Optional[str] = None, + username: Optional[str] = None, + password: Optional[str] = None, + registry_updated: bool = False, + ): + super().__init__(name="fission") + self._docker_registry = registry if registry != "" else None + self._docker_username = username if username != "" else None + self._docker_password = password if password != "" else None + self._registry_updated = registry_updated + self._storage: Optional[MinioConfig] = None + self._storage_updated = False - def serialize(self) -> dict: - return {} + @staticmethod + def typename() -> str: + return "Fission.Resources" + + @property + def docker_registry(self) -> Optional[str]: + return self._docker_registry + + @property + def docker_username(self) -> Optional[str]: + return self._docker_username + + @property + def docker_password(self) -> Optional[str]: + return self._docker_password + + @property + def storage_config(self) -> Optional[MinioConfig]: + return self._storage + + @property + def storage_updated(self) -> bool: + return self._storage_updated + + @property + def registry_updated(self) -> bool: + return self._registry_updated @staticmethod - def initialize(config: dict, cache: Cache) -> Resources: - pass + def initialize(res: Resources, dct: dict): + ret = cast(FissionResources, res) + ret._docker_registry = dct["registry"] + ret._docker_username = dct["username"] + ret._docker_password = dct["password"] + + @staticmethod + def deserialize(config: dict, cache: Cache, handlers: LoggingHandlers) -> Resources: + + cached_config = cache.get_config("fission") + ret = FissionResources() + if cached_config: + super(FissionResources, FissionResources).initialize( + ret, cached_config["resources"] + ) + + # Check for new config - overrides but check if it's different + if "docker_registry" in config: + + ret.logging.info("Using user-provided Docker registry for Fission.") + FissionResources.initialize(ret, config["docker_registry"]) + ret.logging_handlers = handlers + + # check if there has been an update + if not ( + cached_config + and "resources" in cached_config + and "docker" in cached_config["resources"] + and cached_config["resources"]["docker"] == config["docker_registry"] + ): + ret._registry_updated = True + + # Load cached values + elif ( + cached_config + and "resources" in cached_config + and "docker" in cached_config["resources"] + ): + FissionResources.initialize(ret, cached_config["resources"]["docker"]) + ret.logging_handlers = handlers + ret.logging.info("Using cached Docker registry for Fission") + else: + ret = FissionResources() + ret.logging.info("Using default Docker registry for Fission.") + ret.logging_handlers = handlers + ret._registry_updated = True + + # Check for new config + if "storage" in config: + ret._storage = MinioConfig.deserialize(config["storage"]) + ret.logging.info("Using user-provided configuration of storage for Fission.") + + # check if there has been an update + if not ( + cached_config + and "resources" in cached_config + and "storage" in cached_config["resources"] + and cached_config["resources"]["storage"] == config["storage"] + ): + ret.logging.info( + "User-provided configuration is different from cached storage, " + "we will update existing Fission actions." + ) + ret._storage_updated = True + + # Load cached values + elif ( + cached_config + and "resources" in cached_config + and "storage" in cached_config["resources"] + ): + ret._storage = MinioConfig.deserialize(cached_config["resources"]["storage"]) + ret.logging.info("Using cached configuration of storage for Fission.") + + return ret + + + def create_package(self, package_name: str, path: str, env_name: str) -> None: + # PK: Add looger + # logging.info(f"Deploying fission package...") + print(f"Deploying fission package...") + try: + packages = subprocess.run( + "fission package list".split(), stdout=subprocess.PIPE, check=True + ) + subprocess.run( + f"grep {package_name}".split(), + check=True, + input=packages.stdout, + stdout=subprocess.DEVNULL, + ) + # logging.info("Package already exist") + print("Package already exist") + except subprocess.CalledProcessError: + process = f"fission package create --sourcearchive {path} \ + --name {package_name} --env {env_name} --buildcmd ./build.sh" + subprocess.run(process.split(), check=True) + # logging.info("Waiting for package build...") + print("Waiting for package build...") + while True: + try: + packageStatus = subprocess.run( + f"fission package info --name {package_name}".split(), + stdout=subprocess.PIPE, + ) + subprocess.run( + f"grep succeeded".split(), + check=True, + input=packageStatus.stdout, + stderr=subprocess.DEVNULL, + ) + break + except subprocess.CalledProcessError: + if "failed" in packageStatus.stdout.decode("utf-8"): + #logging.error("Build package failed") + print("Build package failed") + raise Exception("Build package failed") + sleep(3) + continue + # logging.info("Package ready") + print("Package ready") + + + def create_enviroment(self, name: str, image: str, builder: str): + print("Add logic to create the enviroment") + # Here we need to create enviroment if it does not exist else get it from the cache + # PK: ADD Caching mechasim here so that not to create enviroment every time or query the enviroment everytime + try: + fission_env_list = subprocess.run( + "fission env list ".split(), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + subprocess.run( + f"grep {name}".split(), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + input=fission_env_list.stdout, + ) + # PK: Add Logger + print(f"Env {name} already exist") + # ret.logging.info(f"Env {name} already exist") + except subprocess.CalledProcessError: + # PK: Add Logger + # ret.logging.info(f'Creating env for {name} using image "{image}".') + print(f'Creating env for {name} using image "{image}".') + try: + subprocess.run( + f"fission env create --name {name} --image {image} \ + --builder {builder}".split(), + check=True, + stdout=subprocess.DEVNULL, + ) + print(f"Successfully created the enviroment {name}") + except subprocess.CalledProcessError: + print(f"Creating env {name} failed.") + # PK: Add Logger + # logging.info(f"Creating env {name} failed. Retrying...") + # PK: Add Logger + + + def update_cache(self, cache: Cache): + super().update_cache(cache) + cache.update_config( + val=self.docker_registry, keys=["fission", "resources", "docker", "registry"] + ) + cache.update_config( + val=self.docker_username, keys=["fission", "resources", "docker", "username"] + ) + cache.update_config( + val=self.docker_password, keys=["fission", "resources", "docker", "password"] + ) + if self._storage: + self._storage.update_cache(["fission", "resources", "storage"], cache) + + def serialize(self) -> dict: + out: dict = { + **super().serialize(), + "docker_registry": self.docker_registry, + "docker_username": self.docker_username, + "docker_password": self.docker_password, + } + if self._storage: + out = {**out, "storage": self._storage.serialize()} + return out + + class FissionConfig(Config): @@ -32,19 +259,52 @@ class FissionConfig(Config): shouldShutdown: bool def __init__(self, config: dict, cache: Cache): - self.name = config["name"] - self.shouldShutdown = config["shouldShutdown"] + super().__init__(name="fission") + self._credentials = FissionCredentials() + self._resources = FissionResources() + self.shutdownStorage = config["shutdownStorage"] + self.removeCluster = config["removeCluster"] + self.fission_exec = config["fissionExec"] self.cache = cache + + @property + def credentials(self) -> FissionCredentials: + return self._credentials - @staticmethod - def initialize(config: dict, cache: Cache) -> Config: - return FissionConfig(config, cache) - - def credentials(self) -> Credentials: - pass + @property + def resources(self) -> FissionResources: + return self._resources - def resources(self) -> Resources: + @staticmethod + def initialize(cfg: Config, dct: dict): pass def serialize(self) -> dict: - pass + return { + "name": "fission", + "shutdownStorage": self.shutdownStorage, + "removeCluster": self.removeCluster, + "fissionExec": self.fission_exec, + "credentials": self._credentials.serialize(), + "resources": self._resources.serialize(), + } + + @staticmethod + def deserialize(config: dict, cache: Cache, handlers: LoggingHandlers) -> Config: + cached_config = cache.get_config("fission") + resources = cast( + FissionResources, FissionResources.deserialize(config, cache, handlers) + ) + + res = FissionConfig(config, cached_config) + res.logging_handlers = handlers + res._resources = resources + return res + + def update_cache(self, cache: Cache): + cache.update_config(val=self.shutdownStorage, keys=["fission", "shutdownStorage"]) + cache.update_config(val=self.removeCluster, keys=["fission", "removeCluster"]) + cache.update_config(val=self.fission_exec, keys=["fission", "fissionExec"]) + self.resources.update_cache(cache) + + diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 93b29cee..061c4a85 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -2,6 +2,8 @@ import os import shutil import subprocess +from typing import cast, Dict, List, Optional, Tuple, Type + import docker import json from time import sleep @@ -9,12 +11,15 @@ from sebs.faas.storage import PersistentStorage from sebs.cache import Cache from sebs.config import SeBSConfig -from sebs.faas.function import Function +from sebs.faas.function import Function, Trigger, ExecutionResult from sebs.faas.system import System -from sebs.fission.fissionFunction import FissionFunction +from sebs.utils import DOCKER_DIR, LoggingHandlers, execute +# from sebs.fission.fissionFunction import FissionFunction from sebs.benchmark import Benchmark from sebs.fission.config import FissionConfig -from sebs.fission.minio import Minio +from sebs.fission.storage import Minio +from .function import FissionFunction, FissionFunctionConfig + from tools.fission_preparation import ( check_if_minikube_installed, run_minikube, @@ -25,349 +30,997 @@ class Fission(System): - language_image: str - language_builder: str - storage: Minio - httpTriggerName: str - functionName: str - packageName: str - envName: str - shouldCallBuilder: bool = False _config: FissionConfig def __init__( self, - sebs_config: SeBSConfig, + system_config: SeBSConfig, config: FissionConfig, cache_client: Cache, docker_client: docker.client, + logger_handlers: LoggingHandlers, ): - super().__init__(sebs_config, cache_client, docker_client) - self._added_functions: List[str] = [] + super().__init__(system_config, cache_client, docker_client) self._config = config + self.logging_handlers = logger_handlers - @staticmethod - def name(): - return "fission" + if self.config.resources.docker_username: + if self.config.resources.docker_registry: + docker_client.login( + username=self.config.resources.docker_username, + password=self.config.resources.docker_password, + registry=self.config.resources.docker_registry, + ) + else: + docker_client.login( + username=self.config.resources.docker_username, + password=self.config.resources.docker_password, + ) + + def initialize(self, config: Dict[str, str] = {}, resource_prefix: Optional[str] = None): + self.initialize_resources(select_prefix=resource_prefix) @property - def config(self) -> FissionConfig: + def config(self) -> SeBSConfig: return self._config - @staticmethod - def add_port_forwarding(port=5051): - podName = ( - subprocess.run( - f"kubectl --namespace fission get pod -l svc=router -o name".split(), - stdout=subprocess.PIPE, + def get_storage(self, replace_existing: bool = False) -> PersistentStorage: + if not hasattr(self, "storage"): + + if not self.config.resources.storage_config: + raise RuntimeError( + "Fission is missing the configuration of pre-allocated storage!" + ) + self.storage = Minio.deserialize( + self.config.resources.storage_config, self.cache_client, self.config.resources ) - .stdout.decode("utf-8") - .rstrip() + self.storage.logging_handlers = self.logging_handlers + else: + self.storage.replace_existing = replace_existing + return self.storage + + def shutdown(self) -> None: + if hasattr(self, "storage") and self.config.shutdownStorage: + self.storage.stop() + if self.config.removeCluster: + from tools.fission_preparation import delete_cluster # type: ignore + + delete_cluster() + super().shutdown() + + @staticmethod + def name() -> str: + return "fission" + + @staticmethod + def typename(): + return "Fission" + + @staticmethod + def function_type() -> "Type[Function]": + return FissionFunction + + # not sure waht is this + def get_fission_cmd(self) -> List[str]: + cmd = [self.config.fission_exec] + # if self.config.wsk_bypass_security: + # cmd.append("-i") + return cmd + + # not sure if if required + def find_image(self, repository_name, image_tag) -> bool: + + if self.config.experimentalManifest: + try: + # This requires enabling experimental Docker features + # Furthermore, it's not yet supported in the Python library + execute(f"docker manifest inspect {repository_name}:{image_tag}") + return True + except RuntimeError: + return False + else: + try: + # default version requires pulling for an image + self.docker_client.images.pull(repository=repository_name, tag=image_tag) + return True + except docker.errors.NotFound: + return False + + def build_base_image( + self, + directory: str, + language_name: str, + language_version: str, + benchmark: str, + is_cached: bool, + ) -> bool: + print("the build base iamge") + """ + When building function for the first time (according to SeBS cache), + check if Docker image is available in the registry. + If yes, then skip building. + If no, then continue building. + + For every subsequent build, we rebuild image and push it to the + registry. These are triggered by users modifying code and enforcing + a build. + """ + + # We need to retag created images when pushing to registry other + # than default + registry_name = self.config.resources.docker_registry + repository_name = self.system_config.docker_repository() + image_tag = self.system_config.benchmark_image_tag( + self.name(), benchmark, language_name, language_version ) - subprocess.Popen( - f"kubectl --namespace fission port-forward {podName} {port}:8888".split(), - stderr=subprocess.DEVNULL, + if registry_name is not None and registry_name != "": + repository_name = f"{registry_name}/{repository_name}" + else: + registry_name = "Docker Hub" + + # Check if we the image is already in the registry. + # cached package, rebuild not enforced -> check for new one + if is_cached: + if self.find_image(repository_name, image_tag): + self.logging.info( + f"Skipping building Fission Docker package for {benchmark}, using " + f"Docker image {repository_name}:{image_tag} from registry: " + f"{registry_name}." + ) + return False + else: + # image doesn't exist, let's continue + self.logging.info( + f"Image {repository_name}:{image_tag} doesn't exist in the registry, " + f"building Fission package for {benchmark}." + ) + + build_dir = os.path.join(directory, "docker") + os.makedirs(build_dir, exist_ok=True) + shutil.copy( + os.path.join(DOCKER_DIR, self.name(), language_name, "Dockerfile.function"), + os.path.join(build_dir, "Dockerfile"), ) - def shutdown(self) -> None: - if self.config.shouldShutdown: - if hasattr(self, "httpTriggerName"): - subprocess.run(f"fission httptrigger delete --name {self.httpTriggerName}".split()) - if hasattr(self, "functionName"): - subprocess.run(f"fission fn delete --name {self.functionName}".split()) - if hasattr(self, "packageName"): - subprocess.run(f"fission package delete --name {self.packageName}".split()) - if hasattr(self, "envName"): - subprocess.run(f"fission env delete --name {self.envName}".split()) - stop_minikube() - self.storage.storage_container.kill() - logging.info("Minio stopped") + for fn in os.listdir(directory): + if fn not in ("index.js", "__main__.py"): + file = os.path.join(directory, fn) + shutil.move(file, build_dir) - def get_storage(self, replace_existing: bool = False) -> PersistentStorage: - self.storage = Minio(self.docker_client) - return self.storage + with open(os.path.join(build_dir, ".dockerignore"), "w") as f: + f.write("Dockerfile") - def initialize(self, config: Dict[str, str] = None): - if config is None: - config = {} + builder_image = self.system_config.benchmark_base_images(self.name(), language_name)[ + language_version + ] + self.logging.info(f"Build the benchmark base image {repository_name}:{image_tag}.") + print("THE BUIDER Image is", builder_image) + + buildargs = {"VERSION": language_version, "BASE_IMAGE": builder_image} + print(f"{repository_name}:{image_tag}") + print("Build dir is", build_dir) + print("Build Argument is", buildargs) + image, _ = self.docker_client.images.build( + tag=f"{repository_name}:{image_tag}", path=build_dir, buildargs=buildargs + ) + + # Now push the image to the registry + # image will be located in a private repository + self.logging.info( + f"Push the benchmark base image {repository_name}:{image_tag} " + f"to registry: {registry_name}." + ) + + # PK: PUshing the Image function is not implemented as of now + + # ret = self.docker_client.images.push( + # repository=repository_name, tag=image_tag, stream=True, decode=True + # ) + # # doesn't raise an exception for some reason + # for val in ret: + # if "error" in val: + # self.logging.error(f"Failed to push the image to registry {registry_name}") + # raise RuntimeError(val) + return True + + def update_build_script(self, scriptPath): + build_script_path = scriptPath + "/" + "build.sh" + print("the build script pathh is", build_script_path) + subprocess.run(["chmod", "+x", build_script_path]) + + def create_zip_directory_fission(directory): + pass + + # packaging code will be differend + def package_code( + self, + directory: str, + language_name: str, + language_version: str, + benchmark: str, + is_cached: bool, + ) -> Tuple[str, int]: - check_if_minikube_installed() - check_if_k8s_installed() - check_if_helm_installed() - run_minikube() - Fission.add_port_forwarding() - sleep(5) + # self.build_base_image(directory, language_name, language_version, benchmark, is_cached) - def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: + # repo_name = self._system_config.docker_repository() + # image_name = "build.{deployment}.{language}.{runtime}".format( + # deployment=self._deployment_name, + # language=language_name, + # runtime=language_version, + # ) + print("DOM SOMETHIGN AFTERTHEE BASE IAMGE") + print("THE BENCHMARK IS", benchmark) + # FIrst we will create an encviroment here + # 3 paramters + # name : langauge + enviroment_name = language_name + language_version.replace(".","") + print("THE ENVVVVV name is", enviroment_name) + builder_image = self.system_config.benchmark_base_images(self.name(), language_name)[ + language_version + ] + builder_image = "spcleth/serverless-benchmarks:build.fission.python.3.8" + runtime_image = builder_image - benchmark.build() + # First we need to create the enviroment + print("the run tiume ", runtime_image) + print("The builder is", builder_image) + self.config.resources.create_enviroment(name = enviroment_name, image = runtime_image, builder = builder_image) + print("The directory is", directory) + self.update_build_script(directory) + + # After Creating enviroment we need to create the package. + # For creating package, we need to create a zip file with the code and dependency. + # PK: I tried zipping the depenecy and using that but it does niot seem to work on fission so we need to build it again + + + # We deploy Minio config in code package since this depends on local + # deployment - it cannnot be a part of Docker image CONFIG_FILES = { - "python": [ - "handler.py", - "requirements.txt", - ".python_packages", - "build.sh", - ], - "nodejs": ["handler.js", "package.json", "node_modules"], - } - directory = benchmark.code_location - package_config = CONFIG_FILES[benchmark.language_name] - function_dir = os.path.join(directory, "function") - os.makedirs(function_dir) - minioConfig = open("./code/minioConfig.json", "w+") - minioConfigJson = { - "access_key": self.storage.access_key, - "secret_key": self.storage.secret_key, - "url": self.storage.url, + "python": ["__main__.py"], + "nodejs": ["index.js"], } - minioConfig.write(json.dumps(minioConfigJson)) - minioConfig.close() - scriptPath = os.path.join(directory, "build.sh") - self.shouldCallBuilder = True - f = open(scriptPath, "w+") - f.write( - "#!/bin/sh\npip3 install -r ${SRC_PKG}/requirements.txt -t \ -${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}" - ) - f.close() - subprocess.run(["chmod", "+x", scriptPath]) - for file in os.listdir(directory): - if file not in package_config: - file = os.path.join(directory, file) - shutil.move(file, function_dir) - os.chdir(directory) + package_config = CONFIG_FILES[language_name] + + benchmark_archive = os.path.join(directory, f"{benchmark}.zip") + # directory = directory + "/" + "docker" + zip_command = [ + "zip", + "-r", + benchmark_archive, + ".", + "-x", + ".python_packages/*" + ] + subprocess.run( - "zip -r {}.zip ./".format(benchmark.benchmark).split(), - stdout=subprocess.DEVNULL, + zip_command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=directory, + text=True ) - benchmark_archive = "{}.zip".format(os.path.join(directory, benchmark.benchmark)) - logging.info("Created {} archive".format(benchmark_archive)) + # zip -r benchmark_archive directory -x "docker/.python_packages/*" + # subprocess.run( + # ["zip -r", benchmark_archive] + package_config, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=directory + # ) + # exit(0) + self.logging.info(f"Created {benchmark_archive} archive") bytes_size = os.path.getsize(benchmark_archive) + self.logging.info("Zip archive size {:2f} MB".format(bytes_size / 1024.0 / 1024.0)) + print(benchmark_archive) + print(bytes_size) + print("AT the end of code package packaging") + + # Second we need to create the package , for creating the package we will use the zip file we have just created + package_name = benchmark + "-" + language_name + "-" + language_version + package_name = package_name.replace(".","") + print(package_name) + self.config.resources.create_package(package_name = package_name, path = benchmark_archive, env_name = enviroment_name) + + + print("FIRST ARE WE IN herhe building the IMAGEJFKSFJDKJF") return benchmark_archive, bytes_size - def update_function(self, name: str, env_name: str, code_path: str): - self.create_function(name, env_name, code_path) + def storage_arguments(self) -> List[str]: + storage = cast(Minio, self.get_storage()) + return [ + "-p", + "MINIO_STORAGE_SECRET_KEY", + storage.config.secret_key, + "-p", + "MINIO_STORAGE_ACCESS_KEY", + storage.config.access_key, + "-p", + "MINIO_STORAGE_CONNECTION_URL", + storage.config.address, + ] - def create_env_if_needed(self, name: str, image: str, builder: str): - self.envName = name - try: - fission_env_list = subprocess.run( - "fission env list ".split(), - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - ) - subprocess.run( - f"grep {name}".split(), - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - input=fission_env_list.stdout, - ) - logging.info(f"Env {name} already exist") - except subprocess.CalledProcessError: - logging.info(f'Creating env for {name} using image "{image}".') - try: - subprocess.run( - f"fission env create --name {name} --image {image} \ - --builder {builder}".split(), - check=True, - stdout=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError: - logging.info(f"Creating env {name} failed. Retrying...") - sleep(10) - try: - subprocess.run( - f"fission env create --name {name} --image {image} \ - --builder {builder}".split(), - check=True, - stdout=subprocess.DEVNULL, - ) - except subprocess.CalledProcessError: - self.storage.storage_container.kill() - logging.info("Minio stopped") - self.initialize() - self.create_env_if_needed(name, image, builder) - - def create_function(self, name: str, env_name: str, path: str): - packageName = f"{name}-package" - self.createPackage(packageName, path, env_name) - self.createFunction(packageName, name) - - def createPackage(self, packageName: str, path: str, envName: str) -> None: - logging.info(f"Deploying fission package...") - self.packageName = packageName - try: - packages = subprocess.run( - "fission package list".split(), stdout=subprocess.PIPE, check=True - ) - subprocess.run( - f"grep {packageName}".split(), - check=True, - input=packages.stdout, - stdout=subprocess.DEVNULL, - ) - logging.info("Package already exist") - except subprocess.CalledProcessError: - process = f"fission package create --sourcearchive {path} \ - --name {packageName} --env {envName} --buildcmd ./build.sh" - subprocess.run(process.split(), check=True) - logging.info("Waiting for package build...") - while True: - try: - packageStatus = subprocess.run( - f"fission package info --name {packageName}".split(), - stdout=subprocess.PIPE, - ) - subprocess.run( - f"grep succeeded".split(), - check=True, - input=packageStatus.stdout, - stderr=subprocess.DEVNULL, - ) - break - except subprocess.CalledProcessError: - if "failed" in packageStatus.stdout.decode("utf-8"): - logging.error("Build package failed") - raise Exception("Build package failed") - sleep(3) - continue - logging.info("Package ready") - - def deletePackage(self, packageName: str) -> None: - logging.info(f"Deleting fission package...") - subprocess.run(f"fission package delete --name {packageName}".split()) - - def createFunction(self, packageName: str, name: str) -> None: - triggerName = f"{name}-trigger" - self.functionName = name - self.httpTriggerName = triggerName + + def create_function(self, code_package: Benchmark, func_name: str) -> "FissionFunction": + package_name = func_name.replace(".", "") + func_name = func_name.replace(".", "") + # triggerName = f"{func_name}-trigger" + # self.functionName = func_name + # self.httpTriggerName = triggerName + print("DEPLOYTING FIssin fnc") logging.info(f"Deploying fission function...") + function_cfg = FissionFunctionConfig.from_benchmark(code_package) + function_cfg.storage = cast(Minio, self.get_storage()).config + print("DEPLOYTING FIssin fnc after some fnconfig thing") try: + print("In the try BLock") triggers = subprocess.run( f"fission fn list".split(), stdout=subprocess.PIPE, check=True ) subprocess.run( - f"grep {name}".split(), + f"grep {func_name}".split(), check=True, input=triggers.stdout, stdout=subprocess.DEVNULL, ) - logging.info(f"Function {name} already exist") + res = FissionFunction( + func_name, code_package.benchmark, code_package.hash, + function_cfg + ) + # PK: Need to do: self.update_function(res, code_package) + print("Fission function already exsit") + logging.info(f"Function {func_name} already exist") except subprocess.CalledProcessError: + print("Before except") + # subprocess.run( + # [ + # *self.get_fission_cmd(), + # "fn", + # "create", + # "--name", + # func_name, + # "--pkg", + # package_name, + # "--entrypoint", + # "function.handler", + # "--fntimeout", + # next(iter({str(code_package.benchmark_config.timeout)})), + # "--maxmemory", + # next(iter({str(code_package.benchmark_config.memory)})) + # ], + # stderr=subprocess.PIPE, + # stdout=subprocess.PIPE, + # check=True, + # ) + # subprocess.run( - f"fission fn create --name {name} --pkg {packageName} \ - --entrypoint handler.handler --env {self.envName}".split(), + [ + *self.get_fission_cmd(), + "fn", + "create", + "--name", + func_name, + "--pkg", + package_name, + "--entrypoint", + "function.handler", + "--fntimeout", + '30000000000', + "--maxmemory", + next(iter({str(code_package.benchmark_config.memory)})) + ], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, check=True, ) + print("Afer except") + res = FissionFunction( + func_name, code_package.benchmark, code_package.hash, function_cfg + ) + # PK:########## ADDING TRIGGERs + # try: + # triggers = subprocess.run( + # f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True + # ) + # subprocess.run( + # f"grep {triggerName}".split(), + # check=True, + # input=triggers.stdout, + # stdout=subprocess.DEVNULL, + # ) + # logging.info(f"Trigger {triggerName} already exist") + # except subprocess.CalledProcessError: + # subprocess.run( + # f"fission httptrigger create --url /benchmark --method POST \ + # --name {triggerName} --function {name}".split(), + # check=True, + # ) + + # self.logging.info("Creating function as an action in Fission.") + # try: + # actions = subprocess.run( + # [*self.get_wsk_cmd(), "action", "list"], + # stderr=subprocess.DEVNULL, + # stdout=subprocess.PIPE, + # ) + # + # function_found = False + # docker_image = "" + # for line in actions.stdout.decode().split("\n"): + # if line and func_name in line.split()[0]: + # function_found = True + # break + # + # function_cfg = FissionConfig.from_benchmark(code_package) + # function_cfg.storage = cast(Minio, self.get_storage()).config + # if function_found: + # # docker image is overwritten by the update + # res = FissionFunction( + # func_name, code_package.benchmark, code_package.hash, function_cfg + # ) + # # Update function - we don't know what version is stored + # self.logging.info(f"Retrieved existing fission action {func_name}.") + # self.update_function(res, code_package) + # else: + # try: + # self.logging.info(f"Creating new FissionF action {func_name}") + # docker_image = self.system_config.benchmark_image_name( + # self.name(), + # code_package.benchmark, + # code_package.language_name, + # code_package.language_version, + # ) + # subprocess.run( + # [ + # *self.get_wsk_cmd(), + # "action", + # "create", + # func_name, + # "--web", + # "true", + # "--docker", + # docker_image, + # "--memory", + # str(code_package.benchmark_config.memory), + # "--timeout", + # str(code_package.benchmark_config.timeout * 1000), + # *self.storage_arguments(), + # code_package.code_location, + # ], + # stderr=subprocess.PIPE, + # stdout=subprocess.PIPE, + # check=True, + # ) + # function_cfg.docker_image = docker_image + # res = FissionFunction( + # func_name, code_package.benchmark, code_package.hash, function_cfg + # ) + # except subprocess.CalledProcessError as e: + # self.logging.error(f"Cannot create action {func_name}.") + # self.logging.error(f"Output: {e.stderr.decode('utf-8')}") + # raise RuntimeError(e) + # + # except FileNotFoundError: + # self.logging.error("Could not retrieve Fission functions - is path to wsk correct?") + # raise RuntimeError("Failed to access wsk binary") + # + # # Add LibraryTrigger to a new function + # trigger = LibraryTrigger(func_name, self.get_wsk_cmd()) + # trigger.logging_handlers = self.logging_handlers + # res.add_trigger(trigger) + # + return res + + def update_function(self, function: Function, code_package: Benchmark): + self.logging.info(f"Update an existing Fission action {function.name}.") + function = cast(FissionFunction, function) + docker_image = self.system_config.benchmark_image_name( + self.name(), + code_package.benchmark, + code_package.language_name, + code_package.language_version, + ) try: - triggers = subprocess.run( - f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True - ) subprocess.run( - f"grep {triggerName}".split(), + [ + *self.get_wsk_cmd(), + "action", + "update", + function.name, + "--web", + "true", + "--docker", + docker_image, + "--memory", + str(code_package.benchmark_config.memory), + "--timeout", + str(code_package.benchmark_config.timeout * 1000), + *self.storage_arguments(), + code_package.code_location, + ], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, check=True, - input=triggers.stdout, - stdout=subprocess.DEVNULL, ) - logging.info(f"Trigger {triggerName} already exist") - except subprocess.CalledProcessError: + function.config.docker_image = docker_image + + except FileNotFoundError as e: + self.logging.error("Could not update Fission function - is path to wsk correct?") + raise RuntimeError(e) + except subprocess.CalledProcessError as e: + self.logging.error(f"Unknown error when running function update: {e}!") + self.logging.error("Make sure to remove SeBS cache after restarting Fission!") + self.logging.error(f"Output: {e.stderr.decode('utf-8')}") + raise RuntimeError(e) + + + def update_function_configuration(self, function: Function, code_package: Benchmark): + self.logging.info(f"Update configuration of an existing Fission action {function.name}.") + try: subprocess.run( - f"fission httptrigger create --url /benchmark --method POST \ - --name {triggerName} --function {name}".split(), + [ + *self.get_wsk_cmd(), + "action", + "update", + function.name, + "--memory", + str(code_package.benchmark_config.memory), + "--timeout", + str(code_package.benchmark_config.timeout * 1000), + *self.storage_arguments(), + ], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, check=True, ) + except FileNotFoundError as e: + self.logging.error("Could not update Fission function - is path to wsk correct?") + raise RuntimeError(e) + except subprocess.CalledProcessError as e: + self.logging.error(f"Unknown error when running function update: {e}!") + self.logging.error("Make sure to remove SeBS cache after restarting Fisiion!") + self.logging.error(f"Output: {e.stderr.decode('utf-8')}") + raise RuntimeError(e) - def deleteFunction(self, name: str) -> None: - logging.info(f"Deleting fission function...") - subprocess.run(f"fission fn delete --name {name}".split()) - - def get_function(self, code_package: Benchmark) -> Function: - self.language_image = self.system_config.benchmark_base_images( - self.name(), code_package.language_name - )["env"] - self.language_builder = self.system_config.benchmark_base_images( - self.name(), code_package.language_name - )["builder"] - path, size = self.package_code(code_package) - benchmark = code_package.benchmark.replace(".", "-") - language = code_package.language_name - language_runtime = code_package.language_version - timeout = code_package.benchmark_config.timeout - memory = code_package.benchmark_config.memory - if code_package.is_cached and code_package.is_cached_valid: - func_name = code_package.cached_config["name"] - code_location = os.path.join( - code_package._cache_client.cache_dir, - code_package._cached_config["code"], - ) - logging.info( - "Using cached function {fname} in {loc}".format(fname=func_name, loc=code_location) - ) - self.create_env_if_needed( - language, - self.language_image, - self.language_builder, - ) - self.update_function(func_name, code_package.language_name, path) - return FissionFunction(func_name) - elif code_package.is_cached: - func_name = code_package.cached_config["name"] - code_location = code_package.code_location - self.create_env_if_needed( - language, - self.language_image, - self.language_builder, - ) - self.update_function(func_name, code_package.language_name, path) - cached_cfg = code_package.cached_config - cached_cfg["code_size"] = size - cached_cfg["timeout"] = timeout - cached_cfg["memory"] = memory - cached_cfg["hash"] = code_package.hash - self.cache_client.update_function( - self.name(), - benchmark.replace("-", "."), - code_package.language_name, - path, - cached_cfg, + + def is_configuration_changed(self, cached_function: Function, benchmark: Benchmark) -> bool: + changed = super().is_configuration_changed(cached_function, benchmark) + + storage = cast(Minio, self.get_storage()) + function = cast(FissionFunction, cached_function) + # check if now we're using a new storage + if function.config.storage != storage.config: + self.logging.info( + "Updating function configuration due to changed storage configuration." ) - code_package.query_cache() - logging.info( - "Updating cached function {fname} in {loc}".format( - fname=func_name, loc=code_location + changed = True + function.config.storage = storage.config + + return changed + + def default_function_name(self, code_package: Benchmark) -> str: + return ( + f"{code_package.benchmark}-{code_package.language_name}-" + f"{code_package.language_version}" + ) + + def enforce_cold_start(self, functions: List[Function], code_package: Benchmark): + raise NotImplementedError() + + def download_metrics( + self, + function_name: str, + start_time: int, + end_time: int, + requests: Dict[str, ExecutionResult], + metrics: dict, + ): + pass + + # def create_trigger(self, function: Function, trigger_type: Trigger.TriggerType) -> Trigger: + # print("THE TRIGGER TYPE IS", trigger_type) + # if trigger_type == Trigger.TriggerType.LIBRARY: + # return function.triggers(Trigger.TriggerType.LIBRARY)[0] + # + # elif trigger_type == Trigger.TriggerType.HTTP: + # try: + # triggers = subprocess.run( + # f"fission httptrigger list".split(), stdout=subprocess.PIPE, + # check=True + # ) + # subprocess.run( + # f"grep {triggerName}".split(), + # check=True, + # input=triggers.stdout, + # stdout=subprocess.DEVNULL, + # ) + # logging.info(f"Trigger {triggerName} already exist") + # + # except subprocess.CalledProcessError: + # subprocess.run( + # f"fission httptrigger create --url /benchmark --method POST \ + # --name {triggerName} --function {name}".split(), + # check=True, + # ) + # + + def create_trigger(self, function: Function, trigger_type: Trigger.TriggerType) -> Trigger: + print("THE TRIGGER TYPE IS", trigger_type) + print("THE Function is", function.name) + triggerName = function.name + "call" + triggerName = triggerName.replace("_", "") + triggerName = triggerName.replace("-", "") + print("THE Trigger Name is", triggerName) + if trigger_type == Trigger.TriggerType.LIBRARY: + return function.triggers(Trigger.TriggerType.LIBRARY)[0] + elif trigger_type == Trigger.TriggerType.HTTP: + try: + triggers = subprocess.run( + f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True ) + subprocess.run( + f"grep {triggerName}".split(), + check=True, + input=triggers.stdout, + stdout=subprocess.DEVNULL,) + logging.info(f"Trigger {triggerName} already exist") + # response = subprocess.run( + # [*self.get_wsk_cmd(), "action", "get", function.name, "--url"], + # stdout=subprocess.PIPE, + # stderr=subprocess.DEVNULL, + # check=True, + # ) + except subprocess.CalledProcessError: + subprocess.run( + f"fission httptrigger create --url /benchmark --method POST \ + --name {triggerName} --function {function.name}".split(), + check=True, ) - return FissionFunction(func_name) + # except FileNotFoundError as e: + # self.logging.error( + # "Could not retrieve Fission configuration - is path to wsk correct?" + # ) + # raise RuntimeError(e) + stdout = response.stdout.decode("utf-8") + print("THE stodout is", stdout) + url = stdout.strip().split("\n")[-1] + ".json" + print("utl", url) + trigger = HTTPTrigger(function.name, url) + trigger.logging_handlers = self.logging_handlers + function.add_trigger(trigger) + self.cache_client.update_function(function) + return trigger else: - code_location = code_package.benchmark_path - func_name = "{}-{}-{}".format(benchmark, language, memory) - self.create_env_if_needed( - language, - self.language_image, - self.language_builder, - ) - self.create_function(func_name, language, path) - self.cache_client.add_function( - deployment=self.name(), - benchmark=benchmark.replace("-", "."), - language=language, - code_package=path, - language_config={ - "name": func_name, - "code_size": size, - "runtime": language_runtime, - "memory": memory, - "timeout": timeout, - "hash": code_package.hash, - }, - storage_config={ - "buckets": { - "input": self.storage.input_buckets, - "output": self.storage.output_buckets, - } - }, - ) - code_package.query_cache() - return FissionFunction(func_name) + raise RuntimeError("Not supported!") + + def cached_function(self, function: Function): + for trigger in function.triggers(Trigger.TriggerType.LIBRARY): + trigger.logging_handlers = self.logging_handlers + cast(LibraryTrigger, trigger).wsk_cmd = self.get_wsk_cmd() + for trigger in function.triggers(Trigger.TriggerType.HTTP): + trigger.logging_handlers = self.logging_handlers + + + +# @staticmethod +# def name(): +# return "fission" +# +# @property +# def config(self) -> FissionConfig: +# return self._config +# +# @staticmethod +# def add_port_forwarding(port=5051): +# podName = ( +# subprocess.run( +# f"kubectl --namespace fission get pod -l svc=router -o name".split(), +# stdout=subprocess.PIPE, +# ) +# .stdout.decode("utf-8") +# .rstrip() +# ) +# subprocess.Popen( +# f"kubectl --namespace fission port-forward {podName} {port}:8888".split(), +# stderr=subprocess.DEVNULL, +# ) +# +# def shutdown(self) -> None: +# if self.config.shouldShutdown: +# if hasattr(self, "httpTriggerName"): +# subprocess.run(f"fission httptrigger delete --name {self.httpTriggerName}".split()) +# if hasattr(self, "functionName"): +# subprocess.run(f"fission fn delete --name {self.functionName}".split()) +# if hasattr(self, "packageName"): +# subprocess.run(f"fission package delete --name {self.packageName}".split()) +# if hasattr(self, "envName"): +# subprocess.run(f"fission env delete --name {self.envName}".split()) +# stop_minikube() +# self.storage.storage_container.kill() +# logging.info("Minio stopped") +# +# def get_storage(self, replace_existing: bool = False) -> PersistentStorage: +# self.storage = Minio(self.docker_client) +# return self.storage +# +# def initialize(self, config: Dict[str, str] = None): +# if config is None: +# config = {} +# +# check_if_minikube_installed() +# check_if_k8s_installed() +# check_if_helm_installed() +# run_minikube() +# Fission.add_port_forwarding() +# sleep(5) +# +# def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: +# +# benchmark.build() +# +# CONFIG_FILES = { +# "python": [ +# "handler.py", +# "requirements.txt", +# ".python_packages", +# "build.sh", +# ], +# "nodejs": ["handler.js", "package.json", "node_modules"], +# } +# directory = benchmark.code_location +# package_config = CONFIG_FILES[benchmark.language_name] +# function_dir = os.path.join(directory, "function") +# os.makedirs(function_dir) +# minioConfig = open("./code/minioConfig.json", "w+") +# minioConfigJson = { +# "access_key": self.storage.access_key, +# "secret_key": self.storage.secret_key, +# "url": self.storage.url, +# } +# minioConfig.write(json.dumps(minioConfigJson)) +# minioConfig.close() +# scriptPath = os.path.join(directory, "build.sh") +# self.shouldCallBuilder = True +# f = open(scriptPath, "w+") +# f.write( +# "#!/bin/sh\npip3 install -r ${SRC_PKG}/requirements.txt -t \ +# ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}" +# ) +# f.close() +# subprocess.run(["chmod", "+x", scriptPath]) +# for file in os.listdir(directory): +# if file not in package_config: +# file = os.path.join(directory, file) +# shutil.move(file, function_dir) +# os.chdir(directory) +# subprocess.run( +# "zip -r {}.zip ./".format(benchmark.benchmark).split(), +# stdout=subprocess.DEVNULL, +# ) +# benchmark_archive = "{}.zip".format(os.path.join(directory, benchmark.benchmark)) +# logging.info("Created {} archive".format(benchmark_archive)) +# bytes_size = os.path.getsize(benchmark_archive) +# return benchmark_archive, bytes_size +# +# def update_function(self, name: str, env_name: str, code_path: str): +# self.create_function(name, env_name, code_path) +# +# def create_env_if_needed(self, name: str, image: str, builder: str): +# self.envName = name +# try: +# fission_env_list = subprocess.run( +# "fission env list ".split(), +# stdout=subprocess.PIPE, +# stderr=subprocess.DEVNULL, +# ) +# subprocess.run( +# f"grep {name}".split(), +# check=True, +# stdout=subprocess.DEVNULL, +# stderr=subprocess.DEVNULL, +# input=fission_env_list.stdout, +# ) +# logging.info(f"Env {name} already exist") +# except subprocess.CalledProcessError: +# logging.info(f'Creating env for {name} using image "{image}".') +# try: +# subprocess.run( +# f"fission env create --name {name} --image {image} \ +# --builder {builder}".split(), +# check=True, +# stdout=subprocess.DEVNULL, +# ) +# except subprocess.CalledProcessError: +# logging.info(f"Creating env {name} failed. Retrying...") +# sleep(10) +# try: +# subprocess.run( +# f"fission env create --name {name} --image {image} \ +# --builder {builder}".split(), +# check=True, +# stdout=subprocess.DEVNULL, +# ) +# except subprocess.CalledProcessError: +# self.storage.storage_container.kill() +# logging.info("Minio stopped") +# self.initialize() +# self.create_env_if_needed(name, image, builder) +# +# def create_function(self, name: str, env_name: str, path: str): +# packageName = f"{name}-package" +# self.createPackage(packageName, path, env_name) +# self.createFunction(packageName, name) +# +# def createPackage(self, packageName: str, path: str, envName: str) -> None: +# logging.info(f"Deploying fission package...") +# self.packageName = packageName +# try: +# packages = subprocess.run( +# "fission package list".split(), stdout=subprocess.PIPE, check=True +# ) +# subprocess.run( +# f"grep {packageName}".split(), +# check=True, +# input=packages.stdout, +# stdout=subprocess.DEVNULL, +# ) +# logging.info("Package already exist") +# except subprocess.CalledProcessError: +# process = f"fission package create --sourcearchive {path} \ +# --name {packageName} --env {envName} --buildcmd ./build.sh" +# subprocess.run(process.split(), check=True) +# logging.info("Waiting for package build...") +# while True: +# try: +# packageStatus = subprocess.run( +# f"fission package info --name {packageName}".split(), +# stdout=subprocess.PIPE, +# ) +# subprocess.run( +# f"grep succeeded".split(), +# check=True, +# input=packageStatus.stdout, +# stderr=subprocess.DEVNULL, +# ) +# break +# except subprocess.CalledProcessError: +# if "failed" in packageStatus.stdout.decode("utf-8"): +# logging.error("Build package failed") +# raise Exception("Build package failed") +# sleep(3) +# continue +# logging.info("Package ready") +# +# def deletePackage(self, packageName: str) -> None: +# logging.info(f"Deleting fission package...") +# subprocess.run(f"fission package delete --name {packageName}".split()) +# +# def createFunction(self, packageName: str, name: str) -> None: +# triggerName = f"{name}-trigger" +# self.functionName = name +# self.httpTriggerName = triggerName +# logging.info(f"Deploying fission function...") +# try: +# triggers = subprocess.run( +# f"fission fn list".split(), stdout=subprocess.PIPE, check=True +# ) +# subprocess.run( +# f"grep {name}".split(), +# check=True, +# input=triggers.stdout, +# stdout=subprocess.DEVNULL, +# ) +# logging.info(f"Function {name} already exist") +# except subprocess.CalledProcessError: +# subprocess.run( +# f"fission fn create --name {name} --pkg {packageName} \ +# --entrypoint handler.handler --env {self.envName}".split(), +# check=True, +# ) +# try: +# triggers = subprocess.run( +# f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True +# ) +# subprocess.run( +# f"grep {triggerName}".split(), +# check=True, +# input=triggers.stdout, +# stdout=subprocess.DEVNULL, +# ) +# logging.info(f"Trigger {triggerName} already exist") +# except subprocess.CalledProcessError: +# subprocess.run( +# f"fission httptrigger create --url /benchmark --method POST \ +# --name {triggerName} --function {name}".split(), +# check=True, +# ) +# +# def deleteFunction(self, name: str) -> None: +# logging.info(f"Deleting fission function...") +# subprocess.run(f"fission fn delete --name {name}".split()) +# +# def get_function(self, code_package: Benchmark) -> Function: +# self.language_image = self.system_config.benchmark_base_images( +# self.name(), code_package.language_name +# )["env"] +# self.language_builder = self.system_config.benchmark_base_images( +# self.name(), code_package.language_name +# )["builder"] +# path, size = self.package_code(code_package) +# benchmark = code_package.benchmark.replace(".", "-") +# language = code_package.language_name +# language_runtime = code_package.language_version +# timeout = code_package.benchmark_config.timeout +# memory = code_package.benchmark_config.memory +# if code_package.is_cached and code_package.is_cached_valid: +# func_name = code_package.cached_config["name"] +# code_location = os.path.join( +# code_package._cache_client.cache_dir, +# code_package._cached_config["code"], +# ) +# logging.info( +# "Using cached function {fname} in {loc}".format(fname=func_name, loc=code_location) +# ) +# self.create_env_if_needed( +# language, +# self.language_image, +# self.language_builder, +# ) +# self.update_function(func_name, code_package.language_name, path) +# return FissionFunction(func_name) +# elif code_package.is_cached: +# func_name = code_package.cached_config["name"] +# code_location = code_package.code_location +# self.create_env_if_needed( +# language, +# self.language_image, +# self.language_builder, +# ) +# self.update_function(func_name, code_package.language_name, path) +# cached_cfg = code_package.cached_config +# cached_cfg["code_size"] = size +# cached_cfg["timeout"] = timeout +# cached_cfg["memory"] = memory +# cached_cfg["hash"] = code_package.hash +# self.cache_client.update_function( +# self.name(), +# benchmark.replace("-", "."), +# code_package.language_name, +# path, +# cached_cfg, +# ) +# code_package.query_cache() +# logging.info( +# "Updating cached function {fname} in {loc}".format( +# fname=func_name, loc=code_location +# ) +# ) +# return FissionFunction(func_name) +# else: +# code_location = code_package.benchmark_path +# func_name = "{}-{}-{}".format(benchmark, language, memory) +# self.create_env_if_needed( +# language, +# self.language_image, +# self.language_builder, +# ) +# self.create_function(func_name, language, path) +# self.cache_client.add_function( +# deployment=self.name(), +# benchmark=benchmark.replace("-", "."), +# language=language, +# code_package=path, +# language_config={ +# "name": func_name, +# "code_size": size, +# "runtime": language_runtime, +# "memory": memory, +# "timeout": timeout, +# "hash": code_package.hash, +# }, +# storage_config={ +# "buckets": { +# "input": self.storage.input_buckets, +# "output": self.storage.output_buckets, +# } +# }, +# ) +# code_package.query_cache() +# return FissionFunction(func_name) diff --git a/sebs/fission/function.py b/sebs/fission/function.py new file mode 100644 index 00000000..cd72fccd --- /dev/null +++ b/sebs/fission/function.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import cast, Optional +from dataclasses import dataclass + +from sebs.benchmark import Benchmark +from sebs.faas.function import Function, FunctionConfig, Runtime +from sebs.storage.config import MinioConfig + + +@dataclass +class FissionFunctionConfig(FunctionConfig): + + # FIXME: merge with higher level abstraction for images + docker_image: str = "" + namespace: str = "_" + storage: Optional[MinioConfig] = None + + @staticmethod + def deserialize(data: dict) -> FissionFunctionConfig: + keys = list(FissionFunctionConfig.__dataclass_fields__.keys()) + data = {k: v for k, v in data.items() if k in keys} + data["runtime"] = Runtime.deserialize(data["runtime"]) + data["storage"] = MinioConfig.deserialize(data["storage"]) + return FissionFunctionConfig(**data) + + def serialize(self) -> dict: + return self.__dict__ + + @staticmethod + def from_benchmark(benchmark: Benchmark) -> FissionFunctionConfig: + return super(FissionFunctionConfig, FissionFunctionConfig)._from_benchmark( + benchmark, FissionFunctionConfig + ) + + +class FissionFunction(Function): + def __init__( + self, name: str, benchmark: str, code_package_hash: str, cfg: FissionFunctionConfig + ): + super().__init__(benchmark, name, code_package_hash, cfg) + + @property + def config(self) -> FissionFunctionConfig: + return cast(FissionFunctionConfig, self._cfg) + + @staticmethod + def typename() -> str: + return "Fission.Function" + + def serialize(self) -> dict: + return {**super().serialize(), "config": self._cfg.serialize()} + + @staticmethod + def deserialize(cached_config: dict) -> FissionFunction: + from sebs.faas.function import Trigger + from sebs.fission.triggers import LibraryTrigger, HTTPTrigger + + cfg = FissionFunctionConfig.deserialize(cached_config["config"]) + ret = FissionFunction( + cached_config["name"], cached_config["benchmark"], cached_config["hash"], cfg + ) + for trigger in cached_config["triggers"]: + trigger_type = cast( + Trigger, + {"Library": LibraryTrigger, "HTTP": HTTPTrigger}.get(trigger["type"]), + ) + assert trigger_type, "Unknown trigger type {}".format(trigger["type"]) + ret.add_trigger(trigger_type.deserialize(trigger)) + return ret + diff --git a/sebs/fission/storage.py b/sebs/fission/storage.py new file mode 100644 index 00000000..08d48021 --- /dev/null +++ b/sebs/fission/storage.py @@ -0,0 +1,28 @@ +import docker + +from sebs.faas.config import Resources +from sebs.storage import minio +from sebs.storage.config import MinioConfig +from sebs.cache import Cache + + +class Minio(minio.Minio): + @staticmethod + def deployment_name() -> str: + return "fission" + + def __init__( + self, + docker_client: docker.client, + cache_client: Cache, + res: Resources, + replace_existing: bool, + ): + super().__init__(docker_client, cache_client, res, replace_existing) + + @staticmethod + def deserialize( + cached_config: MinioConfig, cache_client: Cache, resources: Resources + ) -> "Minio": + return super(Minio, Minio)._deserialize(cached_config, cache_client, resources, Minio) + diff --git a/sebs/fission/triggers.py b/sebs/fission/triggers.py new file mode 100644 index 00000000..939a26d1 --- /dev/null +++ b/sebs/fission/triggers.py @@ -0,0 +1,111 @@ +import concurrent.futures +import datetime +import json +import subprocess +from typing import Dict, List, Optional # noqa + +from sebs.faas.function import ExecutionResult, Trigger + + +class LibraryTrigger(Trigger): + def __init__(self, fname: str, fission_cmd: Optional[List[str]] = None): + super().__init__() + self.fname = fname + if fission_cmd: + self._fission_cmd = [*fission_cmd, "action", "invoke", "--result", self.fname] + + @staticmethod + def trigger_type() -> "Trigger.TriggerType": + return Trigger.TriggerType.LIBRARY + + @property + def fission_cmd(self) -> List[str]: + assert self._fission_cmd + return self._fission_cmd + + @fission_cmd.setter + def fission_cmd(self, fission_cmd: List[str]): + self._fission_cmd = [*fission_cmd, "action", "invoke", "--result", self.fname] + + @staticmethod + def get_command(payload: dict) -> List[str]: + params = [] + for key, value in payload.items(): + params.append("--param") + params.append(key) + params.append(json.dumps(value)) + return params + + def sync_invoke(self, payload: dict) -> ExecutionResult: + command = self.fission_cmd + self.get_command(payload) + error = None + try: + begin = datetime.datetime.now() + response = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True, + ) + end = datetime.datetime.now() + parsed_response = response.stdout.decode("utf-8") + except (subprocess.CalledProcessError, FileNotFoundError) as e: + end = datetime.datetime.now() + error = e + + fission_result = ExecutionResult.from_times(begin, end) + if error is not None: + self.logging.error("Invocation of {} failed!".format(self.fname)) + fission_result.stats.failure = True + return fission_result + + return_content = json.loads(parsed_response) + fission_result.parse_benchmark_output(return_content) + return fission_result + + def async_invoke(self, payload: dict) -> concurrent.futures.Future: + pool = concurrent.futures.ThreadPoolExecutor() + fut = pool.submit(self.sync_invoke, payload) + return fut + + def serialize(self) -> dict: + return {"type": "Library", "name": self.fname} + + @staticmethod + def deserialize(obj: dict) -> Trigger: + return LibraryTrigger(obj["name"]) + + @staticmethod + def typename() -> str: + return "Fission.LibraryTrigger" + + +class HTTPTrigger(Trigger): + def __init__(self, fname: str, url: str): + super().__init__() + self.fname = fname + self.url = url + + @staticmethod + def typename() -> str: + return "Fission.HTTPTrigger" + + @staticmethod + def trigger_type() -> Trigger.TriggerType: + return Trigger.TriggerType.HTTP + + def sync_invoke(self, payload: dict) -> ExecutionResult: + self.logging.debug(f"Invoke function {self.url}") + return self._http_invoke(payload, self.url, False) + + def async_invoke(self, payload: dict) -> concurrent.futures.Future: + pool = concurrent.futures.ThreadPoolExecutor() + fut = pool.submit(self.sync_invoke, payload) + return fut + + def serialize(self) -> dict: + return {"type": "HTTP", "fname": self.fname, "url": self.url} + + @staticmethod + def deserialize(obj: dict) -> Trigger: + return HTTPTrigger(obj["fname"], obj["url"]) diff --git a/sebs/sebs.py b/sebs/sebs.py index 531a98f4..d7d57b9b 100644 --- a/sebs/sebs.py +++ b/sebs/sebs.py @@ -114,8 +114,15 @@ def get_deployment( # FIXME: future annotations, requires Python 3.7+ handlers = self.generate_logging_handlers(logging_filename) + print(deployment_config) + print(config) + print(handlers) + if not deployment_config: deployment_config = Config.deserialize(config, self.cache_client, handlers) + + print("Pritnign implementations of the name") + print(implementations[name]) deployment_client = implementations[name]( self._config, deployment_config, # type: ignore diff --git a/sebs/storage/minio.py b/sebs/storage/minio.py index 1c544a23..4df8820a 100644 --- a/sebs/storage/minio.py +++ b/sebs/storage/minio.py @@ -72,6 +72,7 @@ def start(self, port: int = 9000): self.logging.info("Minio storage ACCESS_KEY={}".format(self._cfg.access_key)) self.logging.info("Minio storage SECRET_KEY={}".format(self._cfg.secret_key)) try: + print("FROM HERE trying for MInio connectioo") self._storage_container = self._docker_client.containers.run( "minio/minio:latest", command="server /data", diff --git a/tools/build_docker_images.py b/tools/build_docker_images.py index 5a5ed046..29f50684 100755 --- a/tools/build_docker_images.py +++ b/tools/build_docker_images.py @@ -10,7 +10,7 @@ parser = argparse.ArgumentParser(description="Run local app experiments.") parser.add_argument( - "--deployment", default=None, choices=["local", "aws", "azure", "gcp"], action="store" + "--deployment", default=None, choices=["local", "aws", "azure", "gcp", "fission"], action="store" ) parser.add_argument("--type", default=None, choices=["build", "run", "manage"], action="store") parser.add_argument("--language", default=None, choices=["python", "nodejs"], action="store") @@ -50,6 +50,7 @@ def build(image_type, system, language=None, version=None, version_name=None): ) ) try: + print("what wer are buildiong", target) client.images.build(path=PROJECT_DIR, dockerfile=dockerfile, buildargs=buildargs, tag=target) except docker.errors.BuildError as exc: print("Error! Build failed!") From 501f2c69abfa3db86040492db883f8111a8e8851 Mon Sep 17 00:00:00 2001 From: prajinkhadka Date: Mon, 5 Aug 2024 22:24:42 -0500 Subject: [PATCH 42/47] WIP -> Main Functionality is working. Need to optimize the code and remove comments --- .../110.dynamic-html/python/function.py | 3 - .../120.uploader/python/function.py | 6 +- benchmarks/wrappers/fission/python/handler.py | 127 +- benchmarks/wrappers/fission/python/storage.py | 2 + config/example.json | 8 +- config/systems.json | 4 +- .../fission/python/Dockerfile.function | 1 + experiments.json | 336 ++++ out_storage.json | 6 +- sebs/faas/function.py | 3 +- sebs/fission/__init__.py | 2 +- sebs/fission/config.py | 21 +- sebs/fission/fission.py | 872 ++-------- sebs/fission/fissionFunction.py | 80 +- sebs/fission/minio.py | 308 ++-- sebs/fission/triggers.py | 1 + testing/original.zip | Bin 0 -> 1948 bytes testing/original/__init__.py | 0 .../python => testing/original}/build.sh | 0 testing/original/original.zip | Bin 0 -> 732 bytes testing/original/requirements.txt | 1 + testing/original/user.py | 2 + .../PyYAML-6.0.1.dist-info/INSTALLER | 1 + .../PyYAML-6.0.1.dist-info/LICENSE | 20 + .../PyYAML-6.0.1.dist-info/METADATA | 46 + .../PyYAML-6.0.1.dist-info/RECORD | 44 + .../PyYAML-6.0.1.dist-info/REQUESTED | 0 .../PyYAML-6.0.1.dist-info/WHEEL | 5 + .../PyYAML-6.0.1.dist-info/top_level.txt | 2 + testing/originalnodep/__init__.py | 0 testing/originalnodep/_yaml/__init__.py | 33 + testing/originalnodep/build.sh | 3 + testing/originalnodep/trying.zip | Bin 0 -> 525678 bytes testing/originalnodep/user.py | 16 + testing/originalnodep/yaml/__init__.py | 390 +++++ testing/originalnodep/yaml/composer.py | 139 ++ testing/originalnodep/yaml/constructor.py | 748 +++++++++ testing/originalnodep/yaml/cyaml.py | 101 ++ testing/originalnodep/yaml/dumper.py | 62 + testing/originalnodep/yaml/emitter.py | 1137 +++++++++++++ testing/originalnodep/yaml/error.py | 75 + testing/originalnodep/yaml/events.py | 86 + testing/originalnodep/yaml/loader.py | 63 + testing/originalnodep/yaml/nodes.py | 49 + testing/originalnodep/yaml/parser.py | 589 +++++++ testing/originalnodep/yaml/reader.py | 185 +++ testing/originalnodep/yaml/representer.py | 389 +++++ testing/originalnodep/yaml/resolver.py | 227 +++ testing/originalnodep/yaml/scanner.py | 1435 +++++++++++++++++ testing/originalnodep/yaml/serializer.py | 111 ++ testing/originalnodep/yaml/tokens.py | 104 ++ .../pacakges/PyYAML-6.0.1.dist-info/INSTALLER | 1 + .../pacakges/PyYAML-6.0.1.dist-info/LICENSE | 20 + .../pacakges/PyYAML-6.0.1.dist-info/METADATA | 46 + .../pacakges/PyYAML-6.0.1.dist-info/RECORD | 44 + .../pacakges/PyYAML-6.0.1.dist-info/REQUESTED | 0 .../pacakges/PyYAML-6.0.1.dist-info/WHEEL | 5 + .../PyYAML-6.0.1.dist-info/top_level.txt | 2 + testing/prajintry/pacakges/_yaml/__init__.py | 33 + testing/prajintry/pacakges/yaml/__init__.py | 390 +++++ testing/prajintry/pacakges/yaml/composer.py | 139 ++ .../prajintry/pacakges/yaml/constructor.py | 748 +++++++++ testing/prajintry/pacakges/yaml/cyaml.py | 101 ++ testing/prajintry/pacakges/yaml/dumper.py | 62 + testing/prajintry/pacakges/yaml/emitter.py | 1137 +++++++++++++ testing/prajintry/pacakges/yaml/error.py | 75 + testing/prajintry/pacakges/yaml/events.py | 86 + testing/prajintry/pacakges/yaml/loader.py | 63 + testing/prajintry/pacakges/yaml/nodes.py | 49 + testing/prajintry/pacakges/yaml/parser.py | 589 +++++++ testing/prajintry/pacakges/yaml/reader.py | 185 +++ .../prajintry/pacakges/yaml/representer.py | 389 +++++ testing/prajintry/pacakges/yaml/resolver.py | 227 +++ testing/prajintry/pacakges/yaml/scanner.py | 1435 +++++++++++++++++ testing/prajintry/pacakges/yaml/serializer.py | 111 ++ testing/prajintry/pacakges/yaml/tokens.py | 104 ++ testing/prajintry/trying.zip | Bin 0 -> 263282 bytes testing/prajintry/user.py | 2 + 78 files changed, 12922 insertions(+), 964 deletions(-) create mode 100644 experiments.json create mode 100644 testing/original.zip create mode 100644 testing/original/__init__.py rename {benchmarks/wrappers/fission/python => testing/original}/build.sh (100%) mode change 100644 => 100755 create mode 100644 testing/original/original.zip create mode 100644 testing/original/requirements.txt create mode 100644 testing/original/user.py create mode 100644 testing/originalnodep/PyYAML-6.0.1.dist-info/INSTALLER create mode 100644 testing/originalnodep/PyYAML-6.0.1.dist-info/LICENSE create mode 100644 testing/originalnodep/PyYAML-6.0.1.dist-info/METADATA create mode 100644 testing/originalnodep/PyYAML-6.0.1.dist-info/RECORD create mode 100644 testing/originalnodep/PyYAML-6.0.1.dist-info/REQUESTED create mode 100644 testing/originalnodep/PyYAML-6.0.1.dist-info/WHEEL create mode 100644 testing/originalnodep/PyYAML-6.0.1.dist-info/top_level.txt create mode 100644 testing/originalnodep/__init__.py create mode 100644 testing/originalnodep/_yaml/__init__.py create mode 100755 testing/originalnodep/build.sh create mode 100644 testing/originalnodep/trying.zip create mode 100644 testing/originalnodep/user.py create mode 100644 testing/originalnodep/yaml/__init__.py create mode 100644 testing/originalnodep/yaml/composer.py create mode 100644 testing/originalnodep/yaml/constructor.py create mode 100644 testing/originalnodep/yaml/cyaml.py create mode 100644 testing/originalnodep/yaml/dumper.py create mode 100644 testing/originalnodep/yaml/emitter.py create mode 100644 testing/originalnodep/yaml/error.py create mode 100644 testing/originalnodep/yaml/events.py create mode 100644 testing/originalnodep/yaml/loader.py create mode 100644 testing/originalnodep/yaml/nodes.py create mode 100644 testing/originalnodep/yaml/parser.py create mode 100644 testing/originalnodep/yaml/reader.py create mode 100644 testing/originalnodep/yaml/representer.py create mode 100644 testing/originalnodep/yaml/resolver.py create mode 100644 testing/originalnodep/yaml/scanner.py create mode 100644 testing/originalnodep/yaml/serializer.py create mode 100644 testing/originalnodep/yaml/tokens.py create mode 100644 testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/INSTALLER create mode 100644 testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/LICENSE create mode 100644 testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/METADATA create mode 100644 testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/RECORD create mode 100644 testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/REQUESTED create mode 100644 testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/WHEEL create mode 100644 testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/top_level.txt create mode 100644 testing/prajintry/pacakges/_yaml/__init__.py create mode 100644 testing/prajintry/pacakges/yaml/__init__.py create mode 100644 testing/prajintry/pacakges/yaml/composer.py create mode 100644 testing/prajintry/pacakges/yaml/constructor.py create mode 100644 testing/prajintry/pacakges/yaml/cyaml.py create mode 100644 testing/prajintry/pacakges/yaml/dumper.py create mode 100644 testing/prajintry/pacakges/yaml/emitter.py create mode 100644 testing/prajintry/pacakges/yaml/error.py create mode 100644 testing/prajintry/pacakges/yaml/events.py create mode 100644 testing/prajintry/pacakges/yaml/loader.py create mode 100644 testing/prajintry/pacakges/yaml/nodes.py create mode 100644 testing/prajintry/pacakges/yaml/parser.py create mode 100644 testing/prajintry/pacakges/yaml/reader.py create mode 100644 testing/prajintry/pacakges/yaml/representer.py create mode 100644 testing/prajintry/pacakges/yaml/resolver.py create mode 100644 testing/prajintry/pacakges/yaml/scanner.py create mode 100644 testing/prajintry/pacakges/yaml/serializer.py create mode 100644 testing/prajintry/pacakges/yaml/tokens.py create mode 100644 testing/prajintry/trying.zip create mode 100644 testing/prajintry/user.py diff --git a/benchmarks/100.webapps/110.dynamic-html/python/function.py b/benchmarks/100.webapps/110.dynamic-html/python/function.py index 7e9d03df..7c990f4e 100644 --- a/benchmarks/100.webapps/110.dynamic-html/python/function.py +++ b/benchmarks/100.webapps/110.dynamic-html/python/function.py @@ -8,11 +8,8 @@ SCRIPT_DIR = path.abspath(path.join(path.dirname(__file__))) - -from flask import request def handler(event): - event = request.get_json(force=True) # start timing name = event.get('username') size = event.get('random_len') diff --git a/benchmarks/100.webapps/120.uploader/python/function.py b/benchmarks/100.webapps/120.uploader/python/function.py index c13656d0..6ef23580 100755 --- a/benchmarks/100.webapps/120.uploader/python/function.py +++ b/benchmarks/100.webapps/120.uploader/python/function.py @@ -5,8 +5,10 @@ import urllib.request -from . import storage -client = storage.storage.get_instance() +# from . import storage +# client = storage.storage.get_instance() +from storage import storage +client = storage.get_instance() def handler(event): diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index d79e3b07..92e9913a 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -1,42 +1,85 @@ -# from flask import request, jsonify, current_app -# -# import json -# import datetime -# import os -# -# -# def handler(): -# body = request.get_data().decode("utf-8") -# current_app.logger.info("Body: " + body) -# event = json.loads(body) -# current_app.logger.info("Event: " + str(event)) -# begin = datetime.datetime.now() -# from function import function -# -# ret = function.handler(event) -# end = datetime.datetime.now() -# current_app.logger.info("Function result: " + str(ret)) -# log_data = {"result": ret["result"]} -# if "measurement" in ret: -# log_data["measurement"] = ret["measurement"] -# -# results_time = (end - begin) / datetime.timedelta(microseconds=1) -# -# # cold test -# is_cold = False -# fname = "cold_run" -# if not os.path.exists(fname): -# is_cold = True -# open(fname, "a").close() -# -# return jsonify( -# json.dumps( -# { -# "begin": begin.strftime("%s.%f"), -# "end": end.strftime("%s.%f"), -# "results_time": results_time, -# "is_cold": is_cold, -# "result": log_data, -# } -# ) -# ) +import datetime, io, json, os, sys, uuid + +from flask import request +from flask import current_app + +# Add current directory to allow location of packages +sys.path.append(os.path.join(os.path.dirname(__file__), '.python_packages/lib/site-packages')) + + +def handler(): + current_app.logger.info("Received request") + event = request.json + income_timestamp = datetime.datetime.now().timestamp() + + # HTTP trigger with API Gateaway + # if 'body' in event: + # event = json.loads(event['body']) + req_id = str(uuid.uuid4()) + # req_id = context.aws_request_id + # event['request-id'] = req_id + event['income-timestamp'] = income_timestamp + begin = datetime.datetime.now() + from function import handler + ret = handler(event) + end = datetime.datetime.now() + + log_data = { + 'output': ret['result'] + } + if 'measurement' in ret: + log_data['measurement'] = ret['measurement'] + if 'logs' in event: + log_data['time'] = (end - begin) / datetime.timedelta(microseconds=1) + results_begin = datetime.datetime.now() + from function import storage + storage_inst = storage.storage.get_instance() + b = event.get('logs').get('bucket') + storage_inst.upload_stream(b, '{}.json'.format(req_id), + io.BytesIO(json.dumps(log_data).encode('utf-8'))) + results_end = datetime.datetime.now() + results_time = (results_end - results_begin) / datetime.timedelta(microseconds=1) + else: + results_time = 0 + + # cold test + is_cold = False + fname = os.path.join('/tmp', 'cold_run') + if not os.path.exists(fname): + is_cold = True + container_id = str(uuid.uuid4())[0:8] + with open(fname, 'a') as f: + f.write(container_id) + else: + with open(fname, 'r') as f: + container_id = f.read() + + cold_start_var = "" + if "cold_start" in os.environ: + cold_start_var = os.environ["cold_start"] + + return { + 'statusCode': 200, + 'begin': begin.strftime('%s.%f'), + 'end': end.strftime('%s.%f'), + 'results_time': results_time, + 'is_cold': is_cold, + 'result': log_data, + 'request_id': req_id, + 'cold_start_var': cold_start_var, + 'container_id': container_id, + } + + # return + # json.dumps({ + # 'begin': begin.strftime('%s.%f'), + # 'end': end.strftime('%s.%f'), + # 'results_time': results_time, + # 'result': log_data, + # 'is_cold': is_cold, + # 'cold_start_var': cold_start_var, + # 'container_id': container_id, + # 'statusCode': 200, + # 'request_id': req_id, + # + # }) diff --git a/benchmarks/wrappers/fission/python/storage.py b/benchmarks/wrappers/fission/python/storage.py index 1d15ff61..f6974bd7 100644 --- a/benchmarks/wrappers/fission/python/storage.py +++ b/benchmarks/wrappers/fission/python/storage.py @@ -27,6 +27,8 @@ def __init__(self): total=5, backoff_factor=0.2, status_forcelist=[500, 502, 503, 504] ) ) + print("WE get from os") + print(os.getenv("MINIO_STORAGE_CONNECTION_URL")) self.client = minio.Minio( os.getenv("MINIO_STORAGE_CONNECTION_URL"), access_key=os.getenv("MINIO_STORAGE_ACCESS_KEY"), diff --git a/config/example.json b/config/example.json index bf5a456c..c5a82263 100644 --- a/config/example.json +++ b/config/example.json @@ -99,11 +99,11 @@ }, "storage": { - "address": "localhost:9011", + "address": "192.168.0.128:9011", "mapped_port": 9011, - "access_key": "kBc9m5lA7Knej-ZDf9I6KRgm9mBAendq3AvriBvDEEI", - "secret_key": "c475e70264063e366f74a63fae338c0ed51eccca2a1d2c79b43945eaa242d355", - "instance_id": "2d92fb4163da76e806bf3969038ee1223562dd2e5ba845dd62b74ed6a0aca8b3", + "access_key": "nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", + "secret_key": "b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", + "instance_id": "4e167c8953ba293e1237313256e3591e4babd9bf0688ead694e20c24ca3b7b7d", "input_buckets": [], "output_buckets": [], "type": "minio" diff --git a/config/systems.json b/config/systems.json index d81d095d..6fcab935 100644 --- a/config/systems.json +++ b/config/systems.json @@ -248,7 +248,7 @@ "images": ["build"], "username": "docker_user", "deployment": { - "files": ["handler.py", "storage.py", "setup.py", "__main__.py", "build.sh"], + "files": ["handler.py", "storage.py", "__main__.py"], "packages": {"minio" : "^5.0.10"} } }, @@ -259,7 +259,7 @@ "16":"fission/node-builder-16" }, "versions": ["12", "14", "16"], - "images": ["build"], + "images": ["function"], "username": "docker_user", "deployment": { "files": ["index.js", "storage.js"], diff --git a/dockerfiles/fission/python/Dockerfile.function b/dockerfiles/fission/python/Dockerfile.function index db2edbad..b997db8e 100644 --- a/dockerfiles/fission/python/Dockerfile.function +++ b/dockerfiles/fission/python/Dockerfile.function @@ -3,4 +3,5 @@ FROM $BASE_IMAGE ARG VERSION ENV PYTHON_VERSION=${VERSION} # COPY . function/ +# add when wokring for contianer ssupport diff --git a/experiments.json b/experiments.json new file mode 100644 index 00000000..957fbea1 --- /dev/null +++ b/experiments.json @@ -0,0 +1,336 @@ +{ + "_invocations": { + "120uploader-python-38": { + "012fe446-6a86-4f8c-b3ed-e12ab3fd677c": { + "billing": { + "_billed_time": null, + "_gb_seconds": 0, + "_memory": null + }, + "output": { + "begin": "1722905362.327837", + "cold_start_var": "", + "container_id": "9e75ae79", + "end": "1722905362.646886", + "is_cold": false, + "request_id": "012fe446-6a86-4f8c-b3ed-e12ab3fd677c", + "result": { + "measurement": { + "compute_time": 304484.0, + "download_size": 0, + "download_time": 0, + "upload_size": 235408, + "upload_time": 14510.0 + }, + "output": { + "bucket": "sebs-benchmarks-a7e49de0", + "key": "120.uploader-0-output/800px-Jammlich_crop.406cf87b.jpg", + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" + } + }, + "results_time": 0, + "statusCode": 200 + }, + "provider_times": { + "execution": 0, + "initialization": 0 + }, + "request_id": "012fe446-6a86-4f8c-b3ed-e12ab3fd677c", + "stats": { + "cold_start": false, + "failure": false, + "memory_used": null + }, + "times": { + "benchmark": 319049, + "client": 329378, + "client_begin": "2024-08-05 19:49:22.347862", + "client_end": "2024-08-05 19:49:22.677240", + "http_first_byte_return": 0.328978, + "http_startup": 0.000804, + "initialization": 0 + } + }, + "0efb7ab0-5366-49a3-857f-118fc07f0437": { + "billing": { + "_billed_time": null, + "_gb_seconds": 0, + "_memory": null + }, + "output": { + "begin": "1722905360.906010", + "cold_start_var": "", + "container_id": "9e75ae79", + "end": "1722905361.561434", + "is_cold": true, + "request_id": "0efb7ab0-5366-49a3-857f-118fc07f0437", + "result": { + "measurement": { + "compute_time": 327144.0, + "download_size": 0, + "download_time": 0, + "upload_size": 235408, + "upload_time": 16698.0 + }, + "output": { + "bucket": "sebs-benchmarks-a7e49de0", + "key": "120.uploader-0-output/800px-Jammlich_crop.a31eec16.jpg", + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" + } + }, + "results_time": 0, + "statusCode": 200 + }, + "provider_times": { + "execution": 0, + "initialization": 0 + }, + "request_id": "0efb7ab0-5366-49a3-857f-118fc07f0437", + "stats": { + "cold_start": true, + "failure": false, + "memory_used": null + }, + "times": { + "benchmark": 655424, + "client": 931981, + "client_begin": "2024-08-05 19:49:20.661627", + "client_end": "2024-08-05 19:49:21.593608", + "http_first_byte_return": 0.931417, + "http_startup": 0.000735, + "initialization": 0 + } + }, + "23a5de95-2445-4fe6-8fed-7c8735322444": { + "billing": { + "_billed_time": null, + "_gb_seconds": 0, + "_memory": null + }, + "output": { + "begin": "1722905359.845015", + "cold_start_var": "", + "container_id": "8deea8d1", + "end": "1722905360.607545", + "is_cold": true, + "request_id": "23a5de95-2445-4fe6-8fed-7c8735322444", + "result": { + "measurement": { + "compute_time": 356009.0, + "download_size": 0, + "download_time": 0, + "upload_size": 235408, + "upload_time": 68016.0 + }, + "output": { + "bucket": "sebs-benchmarks-a7e49de0", + "key": "120.uploader-0-output/800px-Jammlich_crop.e6eb0ea9.jpg", + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" + } + }, + "results_time": 0, + "statusCode": 200 + }, + "provider_times": { + "execution": 0, + "initialization": 0 + }, + "request_id": "23a5de95-2445-4fe6-8fed-7c8735322444", + "stats": { + "cold_start": true, + "failure": false, + "memory_used": null + }, + "times": { + "benchmark": 762530, + "client": 4735457, + "client_begin": "2024-08-05 19:49:15.909896", + "client_end": "2024-08-05 19:49:20.645353", + "http_first_byte_return": 4.733943, + "http_startup": 0.000301, + "initialization": 0 + } + }, + "91508113-fdbc-439f-aee6-409066e1f6e4": { + "billing": { + "_billed_time": null, + "_gb_seconds": 0, + "_memory": null + }, + "output": { + "begin": "1722905361.999195", + "cold_start_var": "", + "container_id": "9e75ae79", + "end": "1722905362.314820", + "is_cold": false, + "request_id": "91508113-fdbc-439f-aee6-409066e1f6e4", + "result": { + "measurement": { + "compute_time": 302069.0, + "download_size": 0, + "download_time": 0, + "upload_size": 235408, + "upload_time": 13410.0 + }, + "output": { + "bucket": "sebs-benchmarks-a7e49de0", + "key": "120.uploader-0-output/800px-Jammlich_crop.31af2db3.jpg", + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" + } + }, + "results_time": 0, + "statusCode": 200 + }, + "provider_times": { + "execution": 0, + "initialization": 0 + }, + "request_id": "91508113-fdbc-439f-aee6-409066e1f6e4", + "stats": { + "cold_start": false, + "failure": false, + "memory_used": null + }, + "times": { + "benchmark": 315625, + "client": 351370, + "client_begin": "2024-08-05 19:49:21.995459", + "client_end": "2024-08-05 19:49:22.346829", + "http_first_byte_return": 0.350798, + "http_startup": 0.000537, + "initialization": 0 + } + }, + "9a0dc19f-91e4-40d7-a859-6d58d4c3a321": { + "billing": { + "_billed_time": null, + "_gb_seconds": 0, + "_memory": null + }, + "output": { + "begin": "1722905361.593417", + "cold_start_var": "", + "container_id": "9e75ae79", + "end": "1722905361.960123", + "is_cold": false, + "request_id": "9a0dc19f-91e4-40d7-a859-6d58d4c3a321", + "result": { + "measurement": { + "compute_time": 353580.0, + "download_size": 0, + "download_time": 0, + "upload_size": 235408, + "upload_time": 13074.0 + }, + "output": { + "bucket": "sebs-benchmarks-a7e49de0", + "key": "120.uploader-0-output/800px-Jammlich_crop.996d77ff.jpg", + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" + } + }, + "results_time": 0, + "statusCode": 200 + }, + "provider_times": { + "execution": 0, + "initialization": 0 + }, + "request_id": "9a0dc19f-91e4-40d7-a859-6d58d4c3a321", + "stats": { + "cold_start": false, + "failure": false, + "memory_used": null + }, + "times": { + "benchmark": 366706, + "client": 394377, + "client_begin": "2024-08-05 19:49:21.600371", + "client_end": "2024-08-05 19:49:21.994748", + "http_first_byte_return": 0.393647, + "http_startup": 0.005644, + "initialization": 0 + } + } + } + }, + "_metrics": {}, + "begin_time": 1722905355.833009, + "config": { + "deployment": { + "credentials": {}, + "fissionExec": "fission", + "name": "fission", + "removeCluster": false, + "resources": { + "benchmarks": "sebs-benchmarks-a7e49de0", + "docker_password": "", + "docker_registry": "", + "docker_username": "", + "resources_id": "a7e49de0", + "storage": { + "access_key": "nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", + "address": "192.168.0.128:9011", + "input_buckets": [], + "instance_id": "4e167c8953ba293e1237313256e3591e4babd9bf0688ead694e20c24ca3b7b7d", + "mapped_port": 9011, + "output_buckets": [], + "secret_key": "b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", + "type": "minio" + } + }, + "shutdownStorage": false + }, + "experiments": { + "download_results": false, + "experiments": { + "eviction-model": { + "function_copy_idx": 0, + "invocations": 1, + "repetitions": 5, + "sleep": 1 + }, + "invocation-overhead": { + "N": 20, + "code_begin": 1048576, + "code_end": 261619712, + "code_points": 20, + "payload_begin": 1024, + "payload_end": 6251000, + "payload_points": 20, + "repetitions": 5, + "type": "payload" + }, + "network-ping-pong": { + "invocations": 50, + "repetitions": 1000, + "threads": 1 + }, + "perf-cost": { + "benchmark": "110.dynamic-html", + "concurrent-invocations": 50, + "experiments": [ + "cold", + "warm", + "burst", + "sequential" + ], + "input-size": "test", + "memory-sizes": [ + 128, + 256 + ], + "repetitions": 50 + } + }, + "flags": {}, + "runtime": { + "language": "python", + "version": "3.8" + }, + "update_code": false, + "update_storage": false + } + }, + "end_time": 1722905362.677655, + "result_bucket": null +} \ No newline at end of file diff --git a/out_storage.json b/out_storage.json index 13df4926..2b6e1586 100644 --- a/out_storage.json +++ b/out_storage.json @@ -1,9 +1,9 @@ { "address": "localhost:9011", "mapped_port": 9011, - "access_key": "kBc9m5lA7Knej-ZDf9I6KRgm9mBAendq3AvriBvDEEI", - "secret_key": "c475e70264063e366f74a63fae338c0ed51eccca2a1d2c79b43945eaa242d355", - "instance_id": "2d92fb4163da76e806bf3969038ee1223562dd2e5ba845dd62b74ed6a0aca8b3", + "access_key": "nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", + "secret_key": "b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", + "instance_id": "4e167c8953ba293e1237313256e3591e4babd9bf0688ead694e20c24ca3b7b7d", "output_buckets": [], "input_buckets": [], "type": "minio" diff --git a/sebs/faas/function.py b/sebs/faas/function.py index c2226cee..01b574a6 100644 --- a/sebs/faas/function.py +++ b/sebs/faas/function.py @@ -200,7 +200,7 @@ def _http_invoke(self, payload: dict, url: str, verify_ssl: bool = True) -> Exec c.setopt(pycurl.SSL_VERIFYPEER, 0) data = BytesIO() c.setopt(pycurl.WRITEFUNCTION, data.write) - + print("the payload is", payload) c.setopt(pycurl.POSTFIELDS, json.dumps(payload)) begin = datetime.now() c.perform() @@ -226,6 +226,7 @@ def _http_invoke(self, payload: dict, url: str, verify_ssl: bool = True) -> Exec raise RuntimeError(f"Cannot process allocation with output: {output}") result.request_id = output["request_id"] # General benchmark output parsing + print("The output from the fucntion returned is", output) result.parse_benchmark_output(output) return result except json.decoder.JSONDecodeError: diff --git a/sebs/fission/__init__.py b/sebs/fission/__init__.py index b1353c64..091ce4f9 100644 --- a/sebs/fission/__init__.py +++ b/sebs/fission/__init__.py @@ -1,3 +1,3 @@ from .fission import Fission # noqa from .config import FissionConfig # noqa -from .minio import Minio # noqa + diff --git a/sebs/fission/config.py b/sebs/fission/config.py index 431f0c7b..96dfd856 100644 --- a/sebs/fission/config.py +++ b/sebs/fission/config.py @@ -7,7 +7,7 @@ from sebs.utils import LoggingHandlers from sebs.storage.config import MinioConfig -from typing import cast, Optional +from typing import cast, Optional, List class FissionCredentials(Credentials): @staticmethod @@ -158,7 +158,7 @@ def create_package(self, package_name: str, path: str, env_name: str) -> None: print("Package already exist") except subprocess.CalledProcessError: process = f"fission package create --sourcearchive {path} \ - --name {package_name} --env {env_name} --buildcmd ./build.sh" + --name {package_name} --env {env_name}" subprocess.run(process.split(), check=True) # logging.info("Waiting for package build...") print("Waiting for package build...") @@ -186,7 +186,7 @@ def create_package(self, package_name: str, path: str, env_name: str) -> None: print("Package ready") - def create_enviroment(self, name: str, image: str, builder: str): + def create_enviroment(self, name: str, image: str, builder: str, runtime_env: List[str] = []): print("Add logic to create the enviroment") # Here we need to create enviroment if it does not exist else get it from the cache # PK: ADD Caching mechasim here so that not to create enviroment every time or query the enviroment everytime @@ -211,9 +211,22 @@ def create_enviroment(self, name: str, image: str, builder: str): # ret.logging.info(f'Creating env for {name} using image "{image}".') print(f'Creating env for {name} using image "{image}".') try: + # PK: Testing + runtime_env = [ + "MINIO_STORAGE_SECRET_KEY=b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", + "MINIO_STORAGE_ACCESS_KEY=nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", + "MINIO_STORAGE_CONNECTION_URL=192.168.0.128:9011" + ] + + connection_uri = runtime_env[2] + access_key = runtime_env[1] + secret_key = runtime_env[0] + print(f"fission env create --name {name} --image {image} --builder {builder} --runtime-env {connection_uri} --runtime-env {access_key} --runtime-env {secret_key}") + print("The runtime env is", runtime_env) + exit(0) subprocess.run( f"fission env create --name {name} --image {image} \ - --builder {builder}".split(), + --builder {builder} --runtime-env {connection_uri} --runtime-env {access_key} --runtime-env {secret_key}".split(), check=True, stdout=subprocess.DEVNULL, ) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 061c4a85..85b72e78 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -11,22 +11,22 @@ from sebs.faas.storage import PersistentStorage from sebs.cache import Cache from sebs.config import SeBSConfig +from sebs.fission.triggers import LibraryTrigger, HTTPTrigger from sebs.faas.function import Function, Trigger, ExecutionResult from sebs.faas.system import System from sebs.utils import DOCKER_DIR, LoggingHandlers, execute -# from sebs.fission.fissionFunction import FissionFunction from sebs.benchmark import Benchmark from sebs.fission.config import FissionConfig from sebs.fission.storage import Minio from .function import FissionFunction, FissionFunctionConfig -from tools.fission_preparation import ( - check_if_minikube_installed, - run_minikube, - check_if_k8s_installed, - check_if_helm_installed, - stop_minikube, -) +# from tools.fission_preparation import ( +# check_if_minikube_installed, +# run_minikube, +# check_if_k8s_installed, +# check_if_helm_installed, +# stop_minikube, +# ) class Fission(System): @@ -100,11 +100,8 @@ def typename(): def function_type() -> "Type[Function]": return FissionFunction - # not sure waht is this def get_fission_cmd(self) -> List[str]: cmd = [self.config.fission_exec] - # if self.config.wsk_bypass_security: - # cmd.append("-i") return cmd # not sure if if required @@ -134,104 +131,96 @@ def build_base_image( benchmark: str, is_cached: bool, ) -> bool: - print("the build base iamge") - """ - When building function for the first time (according to SeBS cache), - check if Docker image is available in the registry. - If yes, then skip building. - If no, then continue building. - - For every subsequent build, we rebuild image and push it to the - registry. These are triggered by users modifying code and enforcing - a build. - """ - - # We need to retag created images when pushing to registry other - # than default - registry_name = self.config.resources.docker_registry - repository_name = self.system_config.docker_repository() - image_tag = self.system_config.benchmark_image_tag( - self.name(), benchmark, language_name, language_version - ) - if registry_name is not None and registry_name != "": - repository_name = f"{registry_name}/{repository_name}" - else: - registry_name = "Docker Hub" - - # Check if we the image is already in the registry. - # cached package, rebuild not enforced -> check for new one - if is_cached: - if self.find_image(repository_name, image_tag): - self.logging.info( - f"Skipping building Fission Docker package for {benchmark}, using " - f"Docker image {repository_name}:{image_tag} from registry: " - f"{registry_name}." - ) - return False - else: - # image doesn't exist, let's continue - self.logging.info( - f"Image {repository_name}:{image_tag} doesn't exist in the registry, " - f"building Fission package for {benchmark}." - ) - - build_dir = os.path.join(directory, "docker") - os.makedirs(build_dir, exist_ok=True) - shutil.copy( - os.path.join(DOCKER_DIR, self.name(), language_name, "Dockerfile.function"), - os.path.join(build_dir, "Dockerfile"), - ) - - for fn in os.listdir(directory): - if fn not in ("index.js", "__main__.py"): - file = os.path.join(directory, fn) - shutil.move(file, build_dir) - - with open(os.path.join(build_dir, ".dockerignore"), "w") as f: - f.write("Dockerfile") - - builder_image = self.system_config.benchmark_base_images(self.name(), language_name)[ - language_version - ] - self.logging.info(f"Build the benchmark base image {repository_name}:{image_tag}.") - print("THE BUIDER Image is", builder_image) - - buildargs = {"VERSION": language_version, "BASE_IMAGE": builder_image} - print(f"{repository_name}:{image_tag}") - print("Build dir is", build_dir) - print("Build Argument is", buildargs) - image, _ = self.docker_client.images.build( - tag=f"{repository_name}:{image_tag}", path=build_dir, buildargs=buildargs - ) - - # Now push the image to the registry - # image will be located in a private repository - self.logging.info( - f"Push the benchmark base image {repository_name}:{image_tag} " - f"to registry: {registry_name}." - ) - - # PK: PUshing the Image function is not implemented as of now - - # ret = self.docker_client.images.push( - # repository=repository_name, tag=image_tag, stream=True, decode=True + pass + # """ + # When building function for the first time (according to SeBS cache), + # check if Docker image is available in the registry. + # If yes, then skip building. + # If no, then continue building. + # + # For every subsequent build, we rebuild image and push it to the + # registry. These are triggered by users modifying code and enforcing + # a build. + # """ + # + # # We need to retag created images when pushing to registry other + # # than default + # registry_name = self.config.resources.docker_registry + # repository_name = self.system_config.docker_repository() + # image_tag = self.system_config.benchmark_image_tag( + # self.name(), benchmark, language_name, language_version # ) - # # doesn't raise an exception for some reason - # for val in ret: - # if "error" in val: - # self.logging.error(f"Failed to push the image to registry {registry_name}") - # raise RuntimeError(val) - return True - - def update_build_script(self, scriptPath): - build_script_path = scriptPath + "/" + "build.sh" - print("the build script pathh is", build_script_path) - subprocess.run(["chmod", "+x", build_script_path]) + # if registry_name is not None and registry_name != "": + # repository_name = f"{registry_name}/{repository_name}" + # else: + # registry_name = "Docker Hub" + # + # # Check if we the image is already in the registry. + # # cached package, rebuild not enforced -> check for new one + # if is_cached: + # if self.find_image(repository_name, image_tag): + # self.logging.info( + # f"Skipping building Fission Docker package for {benchmark}, using " + # f"Docker image {repository_name}:{image_tag} from registry: " + # f"{registry_name}." + # ) + # return False + # else: + # # image doesn't exist, let's continue + # self.logging.info( + # f"Image {repository_name}:{image_tag} doesn't exist in the registry, " + # f"building Fission package for {benchmark}." + # ) + # + # build_dir = os.path.join(directory, "docker") + # os.makedirs(build_dir, exist_ok=True) + # shutil.copy( + # os.path.join(DOCKER_DIR, self.name(), language_name, "Dockerfile.function"), + # os.path.join(build_dir, "Dockerfile"), + # ) + # + # for fn in os.listdir(directory): + # if fn not in ("index.js", "__main__.py"): + # file = os.path.join(directory, fn) + # shutil.move(file, build_dir) + # + # with open(os.path.join(build_dir, ".dockerignore"), "w") as f: + # f.write("Dockerfile") + # + # builder_image = self.system_config.benchmark_base_images(self.name(), language_name)[ + # language_version + # ] + # self.logging.info(f"Build the benchmark base image {repository_name}:{image_tag}.") + # print("THE BUIDER Image is", builder_image) + # + # buildargs = {"VERSION": language_version, "BASE_IMAGE": builder_image} + # print(f"{repository_name}:{image_tag}") + # print("Build dir is", build_dir) + # print("Build Argument is", buildargs) + # image, _ = self.docker_client.images.build( + # tag=f"{repository_name}:{image_tag}", path=build_dir, buildargs=buildargs + # ) + # + # # Now push the image to the registry + # # image will be located in a private repository + # self.logging.info( + # f"Push the benchmark base image {repository_name}:{image_tag} " + # f"to registry: {registry_name}." + # ) + # + # # PK: PUshing the Image function is not implemented as of now + # + # # ret = self.docker_client.images.push( + # # repository=repository_name, tag=image_tag, stream=True, decode=True + # # ) + # # # doesn't raise an exception for some reason + # # for val in ret: + # # if "error" in val: + # # self.logging.error(f"Failed to push the image to registry {registry_name}") + # # raise RuntimeError(val) + # return True - def create_zip_directory_fission(directory): - pass - # packaging code will be differend def package_code( self, directory: str, @@ -240,99 +229,71 @@ def package_code( benchmark: str, is_cached: bool, ) -> Tuple[str, int]: - + + # Use this when wokring for container deployment supports. # self.build_base_image(directory, language_name, language_version, benchmark, is_cached) - # repo_name = self._system_config.docker_repository() - # image_name = "build.{deployment}.{language}.{runtime}".format( - # deployment=self._deployment_name, - # language=language_name, - # runtime=language_version, - # ) - print("DOM SOMETHIGN AFTERTHEE BASE IAMGE") - print("THE BENCHMARK IS", benchmark) - # FIrst we will create an encviroment here - # 3 paramters - # name : langauge enviroment_name = language_name + language_version.replace(".","") - print("THE ENVVVVV name is", enviroment_name) builder_image = self.system_config.benchmark_base_images(self.name(), language_name)[ language_version ] + # PK: Get this from config builder_image = "spcleth/serverless-benchmarks:build.fission.python.3.8" - runtime_image = builder_image - - # First we need to create the enviroment - print("the run tiume ", runtime_image) - print("The builder is", builder_image) + runtime_image = "fission/python-env-3.8" - self.config.resources.create_enviroment(name = enviroment_name, image = runtime_image, builder = builder_image) - print("The directory is", directory) - self.update_build_script(directory) + # PK while creating enviroment , we need to set the env variables for minio + storage_args = self.storage_arguments() + print(storage_args) + # # ['--runtime-env', 'MINIO_STORAGE_SECRET_KEY', 'a126b20a04f11ebfd7b5d68f97e0dbd59e24bcb412a2a37d0feca71d4c035164', + # '--runtime-env', 'MINIO_STORAGE_ACCESS_KEY', 'iwcJwa8y1lCAtpoM4zqdRKr8rD3lzSCA0oET07S1lco', + # '--runtime-env', 'MINIO_STORAGE_CONNECTION_URL', 'localhost:9011'] - # After Creating enviroment we need to create the package. - # For creating package, we need to create a zip file with the code and dependency. - # PK: I tried zipping the depenecy and using that but it does niot seem to work on fission so we need to build it again + self.config.resources.create_enviroment(name = enviroment_name, image = runtime_image, builder = builder_image, runtime_env = storage_args) # We deploy Minio config in code package since this depends on local # deployment - it cannnot be a part of Docker image CONFIG_FILES = { - "python": ["__main__.py"], + "python": [""], "nodejs": ["index.js"], } package_config = CONFIG_FILES[language_name] - benchmark_archive = os.path.join(directory, f"{benchmark}.zip") - # directory = directory + "/" + "docker" - zip_command = [ - "zip", - "-r", - benchmark_archive, - ".", - "-x", - ".python_packages/*" - ] + function_dir = os.path.join(directory, "function") + os.makedirs(function_dir) + # move all files to 'function' except handler.py + for file in os.listdir(directory): + if file not in package_config: + file = os.path.join(directory, file) + shutil.move(file, function_dir) + + # FIXME: use zipfile + # create zip with hidden directory but without parent directory + execute("rm requirements.txt", shell=True, cwd=function_dir) + execute("zip -qu -r9 {}.zip * .".format(benchmark), shell=True, cwd=function_dir) + benchmark_archive = "{}.zip".format(os.path.join(function_dir, benchmark)) + self.logging.info("Created {} archive".format(benchmark_archive)) + + bytes_size = os.path.getsize(os.path.join(function_dir, benchmark_archive)) + mbytes = bytes_size / 1024.0 / 1024.0 + self.logging.info("Zip archive size {:2f} MB".format(mbytes)) - subprocess.run( - zip_command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=directory, - text=True - ) - # zip -r benchmark_archive directory -x "docker/.python_packages/*" - # subprocess.run( - # ["zip -r", benchmark_archive] + package_config, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=directory - # ) - # exit(0) - self.logging.info(f"Created {benchmark_archive} archive") - bytes_size = os.path.getsize(benchmark_archive) - self.logging.info("Zip archive size {:2f} MB".format(bytes_size / 1024.0 / 1024.0)) - print(benchmark_archive) - print(bytes_size) - print("AT the end of code package packaging") - - # Second we need to create the package , for creating the package we will use the zip file we have just created package_name = benchmark + "-" + language_name + "-" + language_version package_name = package_name.replace(".","") - print(package_name) self.config.resources.create_package(package_name = package_name, path = benchmark_archive, env_name = enviroment_name) - - print("FIRST ARE WE IN herhe building the IMAGEJFKSFJDKJF") return benchmark_archive, bytes_size def storage_arguments(self) -> List[str]: storage = cast(Minio, self.get_storage()) return [ - "-p", + "--runtime-env", "MINIO_STORAGE_SECRET_KEY", storage.config.secret_key, - "-p", + "--runtime-env", "MINIO_STORAGE_ACCESS_KEY", storage.config.access_key, - "-p", + "--runtime-env", "MINIO_STORAGE_CONNECTION_URL", storage.config.address, ] @@ -341,16 +302,10 @@ def storage_arguments(self) -> List[str]: def create_function(self, code_package: Benchmark, func_name: str) -> "FissionFunction": package_name = func_name.replace(".", "") func_name = func_name.replace(".", "") - # triggerName = f"{func_name}-trigger" - # self.functionName = func_name - # self.httpTriggerName = triggerName - print("DEPLOYTING FIssin fnc") logging.info(f"Deploying fission function...") function_cfg = FissionFunctionConfig.from_benchmark(code_package) function_cfg.storage = cast(Minio, self.get_storage()).config - print("DEPLOYTING FIssin fnc after some fnconfig thing") try: - print("In the try BLock") triggers = subprocess.run( f"fission fn list".split(), stdout=subprocess.PIPE, check=True ) @@ -364,32 +319,10 @@ def create_function(self, code_package: Benchmark, func_name: str) -> "FissionFu func_name, code_package.benchmark, code_package.hash, function_cfg ) - # PK: Need to do: self.update_function(res, code_package) - print("Fission function already exsit") logging.info(f"Function {func_name} already exist") + logging.info(f"Retrieved existing Fission function {func_name}.") + self.update_function(res, code_package) except subprocess.CalledProcessError: - print("Before except") - # subprocess.run( - # [ - # *self.get_fission_cmd(), - # "fn", - # "create", - # "--name", - # func_name, - # "--pkg", - # package_name, - # "--entrypoint", - # "function.handler", - # "--fntimeout", - # next(iter({str(code_package.benchmark_config.timeout)})), - # "--maxmemory", - # next(iter({str(code_package.benchmark_config.memory)})) - # ], - # stderr=subprocess.PIPE, - # stdout=subprocess.PIPE, - # check=True, - # ) - # subprocess.run( [ *self.get_fission_cmd(), @@ -399,179 +332,73 @@ def create_function(self, code_package: Benchmark, func_name: str) -> "FissionFu func_name, "--pkg", package_name, - "--entrypoint", - "function.handler", - "--fntimeout", - '30000000000', + "--ft", + str(code_package.benchmark_config.timeout * 1000), "--maxmemory", - next(iter({str(code_package.benchmark_config.memory)})) + str(code_package.benchmark_config.memory), + "--entrypoint", + "handler.handler", ], stderr=subprocess.PIPE, stdout=subprocess.PIPE, check=True, ) - print("Afer except") res = FissionFunction( func_name, code_package.benchmark, code_package.hash, function_cfg ) - # PK:########## ADDING TRIGGERs - # try: - # triggers = subprocess.run( - # f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True - # ) - # subprocess.run( - # f"grep {triggerName}".split(), - # check=True, - # input=triggers.stdout, - # stdout=subprocess.DEVNULL, - # ) - # logging.info(f"Trigger {triggerName} already exist") - # except subprocess.CalledProcessError: - # subprocess.run( - # f"fission httptrigger create --url /benchmark --method POST \ - # --name {triggerName} --function {name}".split(), - # check=True, - # ) - - # self.logging.info("Creating function as an action in Fission.") - # try: - # actions = subprocess.run( - # [*self.get_wsk_cmd(), "action", "list"], - # stderr=subprocess.DEVNULL, - # stdout=subprocess.PIPE, - # ) - # - # function_found = False - # docker_image = "" - # for line in actions.stdout.decode().split("\n"): - # if line and func_name in line.split()[0]: - # function_found = True - # break - # - # function_cfg = FissionConfig.from_benchmark(code_package) - # function_cfg.storage = cast(Minio, self.get_storage()).config - # if function_found: - # # docker image is overwritten by the update - # res = FissionFunction( - # func_name, code_package.benchmark, code_package.hash, function_cfg - # ) - # # Update function - we don't know what version is stored - # self.logging.info(f"Retrieved existing fission action {func_name}.") - # self.update_function(res, code_package) - # else: - # try: - # self.logging.info(f"Creating new FissionF action {func_name}") - # docker_image = self.system_config.benchmark_image_name( - # self.name(), - # code_package.benchmark, - # code_package.language_name, - # code_package.language_version, - # ) - # subprocess.run( - # [ - # *self.get_wsk_cmd(), - # "action", - # "create", - # func_name, - # "--web", - # "true", - # "--docker", - # docker_image, - # "--memory", - # str(code_package.benchmark_config.memory), - # "--timeout", - # str(code_package.benchmark_config.timeout * 1000), - # *self.storage_arguments(), - # code_package.code_location, - # ], - # stderr=subprocess.PIPE, - # stdout=subprocess.PIPE, - # check=True, - # ) - # function_cfg.docker_image = docker_image - # res = FissionFunction( - # func_name, code_package.benchmark, code_package.hash, function_cfg - # ) - # except subprocess.CalledProcessError as e: - # self.logging.error(f"Cannot create action {func_name}.") - # self.logging.error(f"Output: {e.stderr.decode('utf-8')}") - # raise RuntimeError(e) - # - # except FileNotFoundError: - # self.logging.error("Could not retrieve Fission functions - is path to wsk correct?") - # raise RuntimeError("Failed to access wsk binary") - # - # # Add LibraryTrigger to a new function - # trigger = LibraryTrigger(func_name, self.get_wsk_cmd()) - # trigger.logging_handlers = self.logging_handlers - # res.add_trigger(trigger) - # return res def update_function(self, function: Function, code_package: Benchmark): self.logging.info(f"Update an existing Fission action {function.name}.") function = cast(FissionFunction, function) - docker_image = self.system_config.benchmark_image_name( - self.name(), - code_package.benchmark, - code_package.language_name, - code_package.language_version, - ) try: subprocess.run( - [ - *self.get_wsk_cmd(), - "action", - "update", - function.name, - "--web", - "true", - "--docker", - docker_image, - "--memory", - str(code_package.benchmark_config.memory), - "--timeout", - str(code_package.benchmark_config.timeout * 1000), - *self.storage_arguments(), - code_package.code_location, - ], - stderr=subprocess.PIPE, - stdout=subprocess.PIPE, - check=True, - ) - function.config.docker_image = docker_image - + [ + *self.get_fission_cmd(), + "fn", + "update", + "--name", + function.name, + "--src", + code_package.code_location, + "--ft", + str(code_package.benchmark_config.timeout * 1000), + "--maxmemory", + str(code_package.benchmark_config.memory), + "--force" + ] + ) except FileNotFoundError as e: - self.logging.error("Could not update Fission function - is path to wsk correct?") + self.logging.error("Could not update Fission function - is path to fission correct?") raise RuntimeError(e) + except subprocess.CalledProcessError as e: self.logging.error(f"Unknown error when running function update: {e}!") self.logging.error("Make sure to remove SeBS cache after restarting Fission!") self.logging.error(f"Output: {e.stderr.decode('utf-8')}") raise RuntimeError(e) - def update_function_configuration(self, function: Function, code_package: Benchmark): self.logging.info(f"Update configuration of an existing Fission action {function.name}.") try: subprocess.run( [ - *self.get_wsk_cmd(), - "action", + *self.get_fission_cmd(), + "fn", "update", + "--name", function.name, - "--memory", + "--maxmemory", str(code_package.benchmark_config.memory), - "--timeout", + "--ft", str(code_package.benchmark_config.timeout * 1000), - *self.storage_arguments(), ], stderr=subprocess.PIPE, stdout=subprocess.PIPE, check=True, ) except FileNotFoundError as e: - self.logging.error("Could not update Fission function - is path to wsk correct?") + self.logging.error("Could not update Fission function - is path to fission correct?") raise RuntimeError(e) except subprocess.CalledProcessError as e: self.logging.error(f"Unknown error when running function update: {e}!") @@ -614,40 +441,11 @@ def download_metrics( ): pass - # def create_trigger(self, function: Function, trigger_type: Trigger.TriggerType) -> Trigger: - # print("THE TRIGGER TYPE IS", trigger_type) - # if trigger_type == Trigger.TriggerType.LIBRARY: - # return function.triggers(Trigger.TriggerType.LIBRARY)[0] - # - # elif trigger_type == Trigger.TriggerType.HTTP: - # try: - # triggers = subprocess.run( - # f"fission httptrigger list".split(), stdout=subprocess.PIPE, - # check=True - # ) - # subprocess.run( - # f"grep {triggerName}".split(), - # check=True, - # input=triggers.stdout, - # stdout=subprocess.DEVNULL, - # ) - # logging.info(f"Trigger {triggerName} already exist") - # - # except subprocess.CalledProcessError: - # subprocess.run( - # f"fission httptrigger create --url /benchmark --method POST \ - # --name {triggerName} --function {name}".split(), - # check=True, - # ) - # - def create_trigger(self, function: Function, trigger_type: Trigger.TriggerType) -> Trigger: - print("THE TRIGGER TYPE IS", trigger_type) - print("THE Function is", function.name) - triggerName = function.name + "call" + triggerName = function.name + "trigger" triggerName = triggerName.replace("_", "") triggerName = triggerName.replace("-", "") - print("THE Trigger Name is", triggerName) + postUrl = triggerName if trigger_type == Trigger.TriggerType.LIBRARY: return function.triggers(Trigger.TriggerType.LIBRARY)[0] elif trigger_type == Trigger.TriggerType.HTTP: @@ -661,27 +459,13 @@ def create_trigger(self, function: Function, trigger_type: Trigger.TriggerType) input=triggers.stdout, stdout=subprocess.DEVNULL,) logging.info(f"Trigger {triggerName} already exist") - # response = subprocess.run( - # [*self.get_wsk_cmd(), "action", "get", function.name, "--url"], - # stdout=subprocess.PIPE, - # stderr=subprocess.DEVNULL, - # check=True, - # ) except subprocess.CalledProcessError: subprocess.run( - f"fission httptrigger create --url /benchmark --method POST \ - --name {triggerName} --function {function.name}".split(), + f"fission httptrigger create --method POST --url /{postUrl} --function {function.name} --name {triggerName}".split(), check=True, ) - # except FileNotFoundError as e: - # self.logging.error( - # "Could not retrieve Fission configuration - is path to wsk correct?" - # ) - # raise RuntimeError(e) - stdout = response.stdout.decode("utf-8") - print("THE stodout is", stdout) - url = stdout.strip().split("\n")[-1] + ".json" - print("utl", url) + # PK: do not encode url , get this from config specified by User. The defualt in config will be localhost and 31314 + url = "http://localhost:31314" + "/" + postUrl trigger = HTTPTrigger(function.name, url) trigger.logging_handlers = self.logging_handlers function.add_trigger(trigger) @@ -693,334 +477,6 @@ def create_trigger(self, function: Function, trigger_type: Trigger.TriggerType) def cached_function(self, function: Function): for trigger in function.triggers(Trigger.TriggerType.LIBRARY): trigger.logging_handlers = self.logging_handlers - cast(LibraryTrigger, trigger).wsk_cmd = self.get_wsk_cmd() + cast(LibraryTrigger, trigger).fission_cmd = self.get_fission_cmd() for trigger in function.triggers(Trigger.TriggerType.HTTP): trigger.logging_handlers = self.logging_handlers - - - -# @staticmethod -# def name(): -# return "fission" -# -# @property -# def config(self) -> FissionConfig: -# return self._config -# -# @staticmethod -# def add_port_forwarding(port=5051): -# podName = ( -# subprocess.run( -# f"kubectl --namespace fission get pod -l svc=router -o name".split(), -# stdout=subprocess.PIPE, -# ) -# .stdout.decode("utf-8") -# .rstrip() -# ) -# subprocess.Popen( -# f"kubectl --namespace fission port-forward {podName} {port}:8888".split(), -# stderr=subprocess.DEVNULL, -# ) -# -# def shutdown(self) -> None: -# if self.config.shouldShutdown: -# if hasattr(self, "httpTriggerName"): -# subprocess.run(f"fission httptrigger delete --name {self.httpTriggerName}".split()) -# if hasattr(self, "functionName"): -# subprocess.run(f"fission fn delete --name {self.functionName}".split()) -# if hasattr(self, "packageName"): -# subprocess.run(f"fission package delete --name {self.packageName}".split()) -# if hasattr(self, "envName"): -# subprocess.run(f"fission env delete --name {self.envName}".split()) -# stop_minikube() -# self.storage.storage_container.kill() -# logging.info("Minio stopped") -# -# def get_storage(self, replace_existing: bool = False) -> PersistentStorage: -# self.storage = Minio(self.docker_client) -# return self.storage -# -# def initialize(self, config: Dict[str, str] = None): -# if config is None: -# config = {} -# -# check_if_minikube_installed() -# check_if_k8s_installed() -# check_if_helm_installed() -# run_minikube() -# Fission.add_port_forwarding() -# sleep(5) -# -# def package_code(self, benchmark: Benchmark) -> Tuple[str, int]: -# -# benchmark.build() -# -# CONFIG_FILES = { -# "python": [ -# "handler.py", -# "requirements.txt", -# ".python_packages", -# "build.sh", -# ], -# "nodejs": ["handler.js", "package.json", "node_modules"], -# } -# directory = benchmark.code_location -# package_config = CONFIG_FILES[benchmark.language_name] -# function_dir = os.path.join(directory, "function") -# os.makedirs(function_dir) -# minioConfig = open("./code/minioConfig.json", "w+") -# minioConfigJson = { -# "access_key": self.storage.access_key, -# "secret_key": self.storage.secret_key, -# "url": self.storage.url, -# } -# minioConfig.write(json.dumps(minioConfigJson)) -# minioConfig.close() -# scriptPath = os.path.join(directory, "build.sh") -# self.shouldCallBuilder = True -# f = open(scriptPath, "w+") -# f.write( -# "#!/bin/sh\npip3 install -r ${SRC_PKG}/requirements.txt -t \ -# ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}" -# ) -# f.close() -# subprocess.run(["chmod", "+x", scriptPath]) -# for file in os.listdir(directory): -# if file not in package_config: -# file = os.path.join(directory, file) -# shutil.move(file, function_dir) -# os.chdir(directory) -# subprocess.run( -# "zip -r {}.zip ./".format(benchmark.benchmark).split(), -# stdout=subprocess.DEVNULL, -# ) -# benchmark_archive = "{}.zip".format(os.path.join(directory, benchmark.benchmark)) -# logging.info("Created {} archive".format(benchmark_archive)) -# bytes_size = os.path.getsize(benchmark_archive) -# return benchmark_archive, bytes_size -# -# def update_function(self, name: str, env_name: str, code_path: str): -# self.create_function(name, env_name, code_path) -# -# def create_env_if_needed(self, name: str, image: str, builder: str): -# self.envName = name -# try: -# fission_env_list = subprocess.run( -# "fission env list ".split(), -# stdout=subprocess.PIPE, -# stderr=subprocess.DEVNULL, -# ) -# subprocess.run( -# f"grep {name}".split(), -# check=True, -# stdout=subprocess.DEVNULL, -# stderr=subprocess.DEVNULL, -# input=fission_env_list.stdout, -# ) -# logging.info(f"Env {name} already exist") -# except subprocess.CalledProcessError: -# logging.info(f'Creating env for {name} using image "{image}".') -# try: -# subprocess.run( -# f"fission env create --name {name} --image {image} \ -# --builder {builder}".split(), -# check=True, -# stdout=subprocess.DEVNULL, -# ) -# except subprocess.CalledProcessError: -# logging.info(f"Creating env {name} failed. Retrying...") -# sleep(10) -# try: -# subprocess.run( -# f"fission env create --name {name} --image {image} \ -# --builder {builder}".split(), -# check=True, -# stdout=subprocess.DEVNULL, -# ) -# except subprocess.CalledProcessError: -# self.storage.storage_container.kill() -# logging.info("Minio stopped") -# self.initialize() -# self.create_env_if_needed(name, image, builder) -# -# def create_function(self, name: str, env_name: str, path: str): -# packageName = f"{name}-package" -# self.createPackage(packageName, path, env_name) -# self.createFunction(packageName, name) -# -# def createPackage(self, packageName: str, path: str, envName: str) -> None: -# logging.info(f"Deploying fission package...") -# self.packageName = packageName -# try: -# packages = subprocess.run( -# "fission package list".split(), stdout=subprocess.PIPE, check=True -# ) -# subprocess.run( -# f"grep {packageName}".split(), -# check=True, -# input=packages.stdout, -# stdout=subprocess.DEVNULL, -# ) -# logging.info("Package already exist") -# except subprocess.CalledProcessError: -# process = f"fission package create --sourcearchive {path} \ -# --name {packageName} --env {envName} --buildcmd ./build.sh" -# subprocess.run(process.split(), check=True) -# logging.info("Waiting for package build...") -# while True: -# try: -# packageStatus = subprocess.run( -# f"fission package info --name {packageName}".split(), -# stdout=subprocess.PIPE, -# ) -# subprocess.run( -# f"grep succeeded".split(), -# check=True, -# input=packageStatus.stdout, -# stderr=subprocess.DEVNULL, -# ) -# break -# except subprocess.CalledProcessError: -# if "failed" in packageStatus.stdout.decode("utf-8"): -# logging.error("Build package failed") -# raise Exception("Build package failed") -# sleep(3) -# continue -# logging.info("Package ready") -# -# def deletePackage(self, packageName: str) -> None: -# logging.info(f"Deleting fission package...") -# subprocess.run(f"fission package delete --name {packageName}".split()) -# -# def createFunction(self, packageName: str, name: str) -> None: -# triggerName = f"{name}-trigger" -# self.functionName = name -# self.httpTriggerName = triggerName -# logging.info(f"Deploying fission function...") -# try: -# triggers = subprocess.run( -# f"fission fn list".split(), stdout=subprocess.PIPE, check=True -# ) -# subprocess.run( -# f"grep {name}".split(), -# check=True, -# input=triggers.stdout, -# stdout=subprocess.DEVNULL, -# ) -# logging.info(f"Function {name} already exist") -# except subprocess.CalledProcessError: -# subprocess.run( -# f"fission fn create --name {name} --pkg {packageName} \ -# --entrypoint handler.handler --env {self.envName}".split(), -# check=True, -# ) -# try: -# triggers = subprocess.run( -# f"fission httptrigger list".split(), stdout=subprocess.PIPE, check=True -# ) -# subprocess.run( -# f"grep {triggerName}".split(), -# check=True, -# input=triggers.stdout, -# stdout=subprocess.DEVNULL, -# ) -# logging.info(f"Trigger {triggerName} already exist") -# except subprocess.CalledProcessError: -# subprocess.run( -# f"fission httptrigger create --url /benchmark --method POST \ -# --name {triggerName} --function {name}".split(), -# check=True, -# ) -# -# def deleteFunction(self, name: str) -> None: -# logging.info(f"Deleting fission function...") -# subprocess.run(f"fission fn delete --name {name}".split()) -# -# def get_function(self, code_package: Benchmark) -> Function: -# self.language_image = self.system_config.benchmark_base_images( -# self.name(), code_package.language_name -# )["env"] -# self.language_builder = self.system_config.benchmark_base_images( -# self.name(), code_package.language_name -# )["builder"] -# path, size = self.package_code(code_package) -# benchmark = code_package.benchmark.replace(".", "-") -# language = code_package.language_name -# language_runtime = code_package.language_version -# timeout = code_package.benchmark_config.timeout -# memory = code_package.benchmark_config.memory -# if code_package.is_cached and code_package.is_cached_valid: -# func_name = code_package.cached_config["name"] -# code_location = os.path.join( -# code_package._cache_client.cache_dir, -# code_package._cached_config["code"], -# ) -# logging.info( -# "Using cached function {fname} in {loc}".format(fname=func_name, loc=code_location) -# ) -# self.create_env_if_needed( -# language, -# self.language_image, -# self.language_builder, -# ) -# self.update_function(func_name, code_package.language_name, path) -# return FissionFunction(func_name) -# elif code_package.is_cached: -# func_name = code_package.cached_config["name"] -# code_location = code_package.code_location -# self.create_env_if_needed( -# language, -# self.language_image, -# self.language_builder, -# ) -# self.update_function(func_name, code_package.language_name, path) -# cached_cfg = code_package.cached_config -# cached_cfg["code_size"] = size -# cached_cfg["timeout"] = timeout -# cached_cfg["memory"] = memory -# cached_cfg["hash"] = code_package.hash -# self.cache_client.update_function( -# self.name(), -# benchmark.replace("-", "."), -# code_package.language_name, -# path, -# cached_cfg, -# ) -# code_package.query_cache() -# logging.info( -# "Updating cached function {fname} in {loc}".format( -# fname=func_name, loc=code_location -# ) -# ) -# return FissionFunction(func_name) -# else: -# code_location = code_package.benchmark_path -# func_name = "{}-{}-{}".format(benchmark, language, memory) -# self.create_env_if_needed( -# language, -# self.language_image, -# self.language_builder, -# ) -# self.create_function(func_name, language, path) -# self.cache_client.add_function( -# deployment=self.name(), -# benchmark=benchmark.replace("-", "."), -# language=language, -# code_package=path, -# language_config={ -# "name": func_name, -# "code_size": size, -# "runtime": language_runtime, -# "memory": memory, -# "timeout": timeout, -# "hash": code_package.hash, -# }, -# storage_config={ -# "buckets": { -# "input": self.storage.input_buckets, -# "output": self.storage.output_buckets, -# } -# }, -# ) -# code_package.query_cache() -# return FissionFunction(func_name) diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py index f44c62a0..d9e0de36 100644 --- a/sebs/fission/fissionFunction.py +++ b/sebs/fission/fissionFunction.py @@ -1,40 +1,40 @@ -from sebs.faas.function import Function, ExecutionResult -import json -import datetime -import requests -import logging - - -class FissionFunction(Function): - def __init__(self, name: str): - super().__init__(name) - - def sync_invoke(self, payload: dict): - url = "http://localhost:5051/benchmark" - readyPayload = json.dumps(payload) - headers = {"content-type": "application/json"} - begin = datetime.datetime.now() - logging.info(f"Function {self.name} invoking...") - response = requests.request("POST", url, data=readyPayload, headers=headers) - end = datetime.datetime.now() - logging.info(f"Function {self.name} returned response with code: {response.status_code}") - fissionResult = ExecutionResult(begin, end) - if response.status_code != 200: - logging.error("Invocation of {} failed!".format(self.name)) - logging.error("Input: {}".format(readyPayload)) - - # TODO: this part is form AWS, need to be rethink - # self._deployment.get_invocation_error( - # function_name=self.name, - # start_time=int(begin.strftime("%s")) - 1, - # end_time=int(end.strftime("%s")) + 1, - # ) - - fissionResult.stats.failure = True - return fissionResult - returnContent = json.loads(json.loads(response.content)) - fissionResult.parse_benchmark_output(returnContent) - return fissionResult - - def async_invoke(self, payload: dict): - raise Exception("Non-trigger invoke not supported!") +# from sebs.faas.function import Function, ExecutionResult +# import json +# import datetime +# import requests +# import logging +# +# +# class FissionFunction(Function): +# def __init__(self, name: str): +# super().__init__(name) +# +# def sync_invoke(self, payload: dict): +# url = "http://localhost:5051/benchmark" +# readyPayload = json.dumps(payload) +# headers = {"content-type": "application/json"} +# begin = datetime.datetime.now() +# logging.info(f"Function {self.name} invoking...") +# response = requests.request("POST", url, data=readyPayload, headers=headers) +# end = datetime.datetime.now() +# logging.info(f"Function {self.name} returned response with code: {response.status_code}") +# fissionResult = ExecutionResult(begin, end) +# if response.status_code != 200: +# logging.error("Invocation of {} failed!".format(self.name)) +# logging.error("Input: {}".format(readyPayload)) +# +# # TODO: this part is form AWS, need to be rethink +# # self._deployment.get_invocation_error( +# # function_name=self.name, +# # start_time=int(begin.strftime("%s")) - 1, +# # end_time=int(end.strftime("%s")) + 1, +# # ) +# +# fissionResult.stats.failure = True +# return fissionResult +# returnContent = json.loads(json.loads(response.content)) +# fissionResult.parse_benchmark_output(returnContent) +# return fissionResult +# +# def async_invoke(self, payload: dict): +# raise Exception("Non-trigger invoke not supported!") diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py index 3fbeff09..ead00c01 100644 --- a/sebs/fission/minio.py +++ b/sebs/fission/minio.py @@ -1,154 +1,154 @@ -#!/usr/bin/env python3 -from sebs.faas.storage import PersistentStorage -from typing import List, Tuple, Any -import logging -from time import sleep -import minio -import secrets -import os - - -class Minio(PersistentStorage): - - storage_container: Any - input_buckets: List[str] = [] - output_buckets: List[str] = [] - input_index = 0 - output_index = 0 - access_key: str = "" - secret_key: str = "" - port = 9000 - location = "fissionBenchmark" - connection: Any - docker_client = None - - def __init__(self, docker_client): - self.docker_client = docker_client - self.start() - sleep(10) - self.connection = self.get_connection() - - def start(self): - self.startMinio() - - def startMinio(self): - minioVersion = "minio/minio:latest" - self.access_key = secrets.token_urlsafe(32) - self.secret_key = secrets.token_hex(32) - logging.info("Minio container starting") - logging.info("ACCESS_KEY={}".format(self.access_key)) - logging.info("SECRET_KEY={}".format(self.secret_key)) - self.storage_container = self.docker_client.containers.run( - minioVersion, - command="server /data", - ports={str(self.port): self.port}, - environment={ - "MINIO_ACCESS_KEY": self.access_key, - "MINIO_SECRET_KEY": self.secret_key, - }, - remove=True, - stdout=True, - stderr=True, - detach=True, - ) - self.storage_container.reload() - networks = self.storage_container.attrs["NetworkSettings"]["Networks"] - self.url = "{IPAddress}:{Port}".format( - IPAddress=networks["bridge"]["IPAddress"], Port=self.port - ) - logging.info("Started minio instance at {}".format(self.url)) - - def get_connection(self): - return minio.Minio( - self.url, - access_key=self.access_key, - secret_key=self.secret_key, - secure=False, - ) - - def input(self) -> List[str]: - return self.input_buckets - - def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: - input_index = self.input_index - bucket_name = "{}-{}-input".format(name, input_index) - exist = self.connection.bucket_exists(bucket_name) - try: - if cache: - self.input_index += 1 - if exist: - return (bucket_name, input_index) - else: - self.connection.make_bucket(bucket_name, location=self.location) - self.input_buckets.append(bucket_name) - return (bucket_name, input_index) - if exist: - return (bucket_name, input_index) - self.connection.make_bucket(bucket_name, location=self.location) - self.input_buckets.append(bucket_name) - return (bucket_name, input_index) - except ( - minio.error.BucketAlreadyOwnedByYou, - minio.error.BucketAlreadyExists, - minio.error.ResponseError, - ) as err: - logging.error("Bucket creation failed!") - raise err - - def add_output_bucket( - self, name: str, suffix: str = "output", cache: bool = True - ) -> Tuple[str, int]: - output_index = self.output_index - bucket_name = "{}-{}-{}".format(name, output_index, suffix) - exist = self.connection.bucket_exists(bucket_name) - try: - if cache: - self.output_index += 1 - if exist: - return (bucket_name, output_index) - else: - self.connection.make_bucket(bucket_name, location=self.location) - self.output_buckets.append(bucket_name) - return (bucket_name, output_index) - if exist: - return (bucket_name, output_index) - self.connection.make_bucket(bucket_name, location=self.location) - self.output_buckets.append(bucket_name) - return (bucket_name, output_index) - except ( - minio.error.BucketAlreadyOwnedByYou, - minio.error.BucketAlreadyExists, - minio.error.ResponseError, - ) as err: - logging.error("Bucket creation failed!") - raise err - - def output(self) -> List[str]: - return self.output_buckets - - def download(self, bucket_name: str, key: str, filepath: str) -> None: - objects = self.connection.list_objects_v2(bucket_name) - objects = [obj.object_name for obj in objects] - for obj in objects: - self.connection.fget_object(bucket_name, obj, os.path.join(filepath, obj)) - - def upload(self, bucket_name: str, filepath: str, key: str): - self.connection.put_object(bucket_name, filepath) - - def list_bucket(self, bucket_name: str) -> List[str]: - buckets = [] - for bucket in self.connection.list_buckets(): - if bucket.name == bucket_name: - buckets.append(bucket.name) - return buckets - - def allocate_buckets(self, benchmark: str, buckets: Tuple[int, int]): - inputNumber = buckets[0] - outputNumber = buckets[1] - for i in range(inputNumber): - self.add_input_bucket(benchmark) - for i in range(outputNumber): - self.add_output_bucket(benchmark) - - def uploader_func(self, bucket_idx: int, file: str, filepath: str) -> None: - pass +# #!/usr/bin/env python3 +# from sebs.faas.storage import PersistentStorage +# from typing import List, Tuple, Any +# import logging +# from time import sleep +# import minio +# import secrets +# import os +# +# +# class Minio(PersistentStorage): +# +# storage_container: Any +# input_buckets: List[str] = [] +# output_buckets: List[str] = [] +# input_index = 0 +# output_index = 0 +# access_key: str = "" +# secret_key: str = "" +# port = 9000 +# location = "fissionBenchmark" +# connection: Any +# docker_client = None +# +# def __init__(self, docker_client): +# self.docker_client = docker_client +# self.start() +# sleep(10) +# self.connection = self.get_connection() +# +# def start(self): +# self.startMinio() +# +# def startMinio(self): +# minioVersion = "minio/minio:latest" +# self.access_key = secrets.token_urlsafe(32) +# self.secret_key = secrets.token_hex(32) +# logging.info("Minio container starting") +# logging.info("ACCESS_KEY={}".format(self.access_key)) +# logging.info("SECRET_KEY={}".format(self.secret_key)) +# self.storage_container = self.docker_client.containers.run( +# minioVersion, +# command="server /data", +# ports={str(self.port): self.port}, +# environment={ +# "MINIO_ACCESS_KEY": self.access_key, +# "MINIO_SECRET_KEY": self.secret_key, +# }, +# remove=True, +# stdout=True, +# stderr=True, +# detach=True, +# ) +# self.storage_container.reload() +# networks = self.storage_container.attrs["NetworkSettings"]["Networks"] +# self.url = "{IPAddress}:{Port}".format( +# IPAddress=networks["bridge"]["IPAddress"], Port=self.port +# ) +# logging.info("Started minio instance at {}".format(self.url)) +# +# def get_connection(self): +# return minio.Minio( +# self.url, +# access_key=self.access_key, +# secret_key=self.secret_key, +# secure=False, +# ) +# +# def input(self) -> List[str]: +# return self.input_buckets +# +# def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: +# input_index = self.input_index +# bucket_name = "{}-{}-input".format(name, input_index) +# exist = self.connection.bucket_exists(bucket_name) +# try: +# if cache: +# self.input_index += 1 +# if exist: +# return (bucket_name, input_index) +# else: +# self.connection.make_bucket(bucket_name, location=self.location) +# self.input_buckets.append(bucket_name) +# return (bucket_name, input_index) +# if exist: +# return (bucket_name, input_index) +# self.connection.make_bucket(bucket_name, location=self.location) +# self.input_buckets.append(bucket_name) +# return (bucket_name, input_index) +# except ( +# minio.error.BucketAlreadyOwnedByYou, +# minio.error.BucketAlreadyExists, +# minio.error.ResponseError, +# ) as err: +# logging.error("Bucket creation failed!") +# raise err +# +# def add_output_bucket( +# self, name: str, suffix: str = "output", cache: bool = True +# ) -> Tuple[str, int]: +# output_index = self.output_index +# bucket_name = "{}-{}-{}".format(name, output_index, suffix) +# exist = self.connection.bucket_exists(bucket_name) +# try: +# if cache: +# self.output_index += 1 +# if exist: +# return (bucket_name, output_index) +# else: +# self.connection.make_bucket(bucket_name, location=self.location) +# self.output_buckets.append(bucket_name) +# return (bucket_name, output_index) +# if exist: +# return (bucket_name, output_index) +# self.connection.make_bucket(bucket_name, location=self.location) +# self.output_buckets.append(bucket_name) +# return (bucket_name, output_index) +# except ( +# minio.error.BucketAlreadyOwnedByYou, +# minio.error.BucketAlreadyExists, +# minio.error.ResponseError, +# ) as err: +# logging.error("Bucket creation failed!") +# raise err +# +# def output(self) -> List[str]: +# return self.output_buckets +# +# def download(self, bucket_name: str, key: str, filepath: str) -> None: +# objects = self.connection.list_objects_v2(bucket_name) +# objects = [obj.object_name for obj in objects] +# for obj in objects: +# self.connection.fget_object(bucket_name, obj, os.path.join(filepath, obj)) +# +# def upload(self, bucket_name: str, filepath: str, key: str): +# self.connection.put_object(bucket_name, filepath) +# +# def list_bucket(self, bucket_name: str) -> List[str]: +# buckets = [] +# for bucket in self.connection.list_buckets(): +# if bucket.name == bucket_name: +# buckets.append(bucket.name) +# return buckets +# +# def allocate_buckets(self, benchmark: str, buckets: Tuple[int, int]): +# inputNumber = buckets[0] +# outputNumber = buckets[1] +# for i in range(inputNumber): +# self.add_input_bucket(benchmark) +# for i in range(outputNumber): +# self.add_output_bucket(benchmark) +# +# def uploader_func(self, bucket_idx: int, file: str, filepath: str) -> None: +# pass diff --git a/sebs/fission/triggers.py b/sebs/fission/triggers.py index 939a26d1..4bd9f1c4 100644 --- a/sebs/fission/triggers.py +++ b/sebs/fission/triggers.py @@ -96,6 +96,7 @@ def trigger_type() -> Trigger.TriggerType: def sync_invoke(self, payload: dict) -> ExecutionResult: self.logging.debug(f"Invoke function {self.url}") + print("THE payload for fission here is", payload) return self._http_invoke(payload, self.url, False) def async_invoke(self, payload: dict) -> concurrent.futures.Future: diff --git a/testing/original.zip b/testing/original.zip new file mode 100644 index 0000000000000000000000000000000000000000..90c61f526aef0fa478de8d47bed6a5901b1e0c80 GIT binary patch literal 1948 zcmWIWW@h1H0D-=MZxLVyl;C8LVaPAaOwY_q%+U`G;bdU`T{17tu3}zVX$3a}Bg
^tbgu-)(IVhuoGvr^*qm<47{Kl6tI$y zuc(jzL)rSP`d+#^XLY>J`kvL+yr#WU11Rk{DJ&p3*hA0ZDpSg?9c({-xHnbCAbGPH z=rE@%kDb|pjsjsxWQP@{7M5lfrRJvQl@#lhRFr@n_!PTe3o0uUb91;r0SNIjC=N>b zUgfL>nyLj1Ujby3ON&#BV1WmU3M_$F+JBa-*+8J>eP!3t$xYKQ&115>DaW;Ry~4_y z4HM)eg`@88yLi=m;m$Jx&t|9Sxbk;@<8!lj^?I<#On1f`^@$l1nNDrozwljPkjH}b zgzHY_x*YKvg?~sEcop2AARV~GKG*%|IZomBH-;(!0imIZTUvrAihpiy`JC3%(j6hp z>c}0}zcA0jd4^GIuG?O{RsHA6>U}qwq-sUYjeWJY`+c2XxAWH?#{Tyj)7)wpai<$@ zptrFl7aWNXdvf6b8iJ8Zu$VxvR04`ioQVUAy9g!@cA$aiX#$HmLo7`&GRZOHDv%_A z*#ZOv7~VR9Xr$ta6;eE56io=@FbgM`aSRMg8l91iLrHr;lR-rlR+F)(JV6GC$-oTE z@V0RxvdM@92{aZ|Y+*GP;(yFy24U=yMo{qp_djO30vZo0bg&watE@pbz6IHMcq$`3 zl)0f{0Sp9SC|5Hvpk{2+1D69Oa1Q_tq$ErQkW5?B7{JVcC5w_ClSqD*0QtWn+yDRo literal 0 HcmV?d00001 diff --git a/testing/original/__init__.py b/testing/original/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/benchmarks/wrappers/fission/python/build.sh b/testing/original/build.sh old mode 100644 new mode 100755 similarity index 100% rename from benchmarks/wrappers/fission/python/build.sh rename to testing/original/build.sh diff --git a/testing/original/original.zip b/testing/original/original.zip new file mode 100644 index 0000000000000000000000000000000000000000..81c786d4f9f70d2901423d31b2f5dc185d8eb699 GIT binary patch literal 732 zcmWIWW@h1H0D;VaZxLVyl;CENVTg~<%*!l^kJl@x3=QFAVBT6XFD<2#}FAXdr!;n;(nUkVdoB=lBI(8F+!>;(BI&)e7 z+yku>ItF1U&S>j-o;ewKK{qI1B_Cf=AODB4^;h-1bal?^c%AhnV136;xIx=H_sLJP0wcHuzh_v%iyBWr2o)Fgwt|(&E%2SP0buLnjY2gi=z| z6mk=3.6 +License-File: LICENSE + +YAML is a data serialization format designed for human readability +and interaction with scripting languages. PyYAML is a YAML parser +and emitter for Python. + +PyYAML features a complete YAML 1.1 parser, Unicode support, pickle +support, capable extension API, and sensible error messages. PyYAML +supports standard YAML tags and provides Python-specific tags that +allow to represent an arbitrary Python object. + +PyYAML is applicable for a broad range of tasks from complex +configuration files to object serialization and persistence. diff --git a/testing/originalnodep/PyYAML-6.0.1.dist-info/RECORD b/testing/originalnodep/PyYAML-6.0.1.dist-info/RECORD new file mode 100644 index 00000000..769558ba --- /dev/null +++ b/testing/originalnodep/PyYAML-6.0.1.dist-info/RECORD @@ -0,0 +1,44 @@ +PyYAML-6.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +PyYAML-6.0.1.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101 +PyYAML-6.0.1.dist-info/METADATA,sha256=UNNF8-SzzwOKXVo-kV5lXUGH2_wDWMBmGxqISpp5HQk,2058 +PyYAML-6.0.1.dist-info/RECORD,, +PyYAML-6.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +PyYAML-6.0.1.dist-info/WHEEL,sha256=FCrbbeH_Uuw2ZMaB8nW-JE7XeUWVfF-XtWcVJYU0Zm8,110 +PyYAML-6.0.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11 +_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402 +_yaml/__pycache__/__init__.cpython-312.pyc,, +yaml/__init__.py,sha256=bhl05qSeO-1ZxlSRjGrvl2m9nrXb1n9-GQatTN0Mrqc,12311 +yaml/__pycache__/__init__.cpython-312.pyc,, +yaml/__pycache__/composer.cpython-312.pyc,, +yaml/__pycache__/constructor.cpython-312.pyc,, +yaml/__pycache__/cyaml.cpython-312.pyc,, +yaml/__pycache__/dumper.cpython-312.pyc,, +yaml/__pycache__/emitter.cpython-312.pyc,, +yaml/__pycache__/error.cpython-312.pyc,, +yaml/__pycache__/events.cpython-312.pyc,, +yaml/__pycache__/loader.cpython-312.pyc,, +yaml/__pycache__/nodes.cpython-312.pyc,, +yaml/__pycache__/parser.cpython-312.pyc,, +yaml/__pycache__/reader.cpython-312.pyc,, +yaml/__pycache__/representer.cpython-312.pyc,, +yaml/__pycache__/resolver.cpython-312.pyc,, +yaml/__pycache__/scanner.cpython-312.pyc,, +yaml/__pycache__/serializer.cpython-312.pyc,, +yaml/__pycache__/tokens.cpython-312.pyc,, +yaml/_yaml.cpython-312-darwin.so,sha256=zLYFDcq5DNWEltMIpJVcuFQ4jRWM4bIJADV_pT4O5wM,361928 +yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883 +yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639 +yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851 +yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837 +yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006 +yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533 +yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445 +yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061 +yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440 +yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495 +yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794 +yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190 +yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004 +yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279 +yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165 +yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573 diff --git a/testing/originalnodep/PyYAML-6.0.1.dist-info/REQUESTED b/testing/originalnodep/PyYAML-6.0.1.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/testing/originalnodep/PyYAML-6.0.1.dist-info/WHEEL b/testing/originalnodep/PyYAML-6.0.1.dist-info/WHEEL new file mode 100644 index 00000000..2e30befe --- /dev/null +++ b/testing/originalnodep/PyYAML-6.0.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.41.2) +Root-Is-Purelib: false +Tag: cp312-cp312-macosx_11_0_arm64 + diff --git a/testing/originalnodep/PyYAML-6.0.1.dist-info/top_level.txt b/testing/originalnodep/PyYAML-6.0.1.dist-info/top_level.txt new file mode 100644 index 00000000..e6475e91 --- /dev/null +++ b/testing/originalnodep/PyYAML-6.0.1.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_yaml +yaml diff --git a/testing/originalnodep/__init__.py b/testing/originalnodep/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testing/originalnodep/_yaml/__init__.py b/testing/originalnodep/_yaml/__init__.py new file mode 100644 index 00000000..7baa8c4b --- /dev/null +++ b/testing/originalnodep/_yaml/__init__.py @@ -0,0 +1,33 @@ +# This is a stub package designed to roughly emulate the _yaml +# extension module, which previously existed as a standalone module +# and has been moved into the `yaml` package namespace. +# It does not perfectly mimic its old counterpart, but should get +# close enough for anyone who's relying on it even when they shouldn't. +import yaml + +# in some circumstances, the yaml module we imoprted may be from a different version, so we need +# to tread carefully when poking at it here (it may not have the attributes we expect) +if not getattr(yaml, '__with_libyaml__', False): + from sys import version_info + + exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError + raise exc("No module named '_yaml'") +else: + from yaml._yaml import * + import warnings + warnings.warn( + 'The _yaml extension module is now located at yaml._yaml' + ' and its location is subject to change. To use the' + ' LibYAML-based parser and emitter, import from `yaml`:' + ' `from yaml import CLoader as Loader, CDumper as Dumper`.', + DeprecationWarning + ) + del warnings + # Don't `del yaml` here because yaml is actually an existing + # namespace member of _yaml. + +__name__ = '_yaml' +# If the module is top-level (i.e. not a part of any specific package) +# then the attribute should be set to ''. +# https://docs.python.org/3.8/library/types.html +__package__ = '' diff --git a/testing/originalnodep/build.sh b/testing/originalnodep/build.sh new file mode 100755 index 00000000..ee2f4c8b --- /dev/null +++ b/testing/originalnodep/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG} +cp -r ${SRC_PKG} ${DEPLOY_PKG} diff --git a/testing/originalnodep/trying.zip b/testing/originalnodep/trying.zip new file mode 100644 index 0000000000000000000000000000000000000000..4d28fb0ef5f4bdf4c176109a71acebba10e3e0d3 GIT binary patch literal 525678 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

OK`j6<-}Ubxk&@HmSQ8Xjz$VouvSzBQ>-1 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πDmW3lX)m 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

S6QrU!e7N_Gc^zNs09ti1RN=v0F!OgC7I^Lz{ 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≥%gYZ!yV3b!if2#1_}^!*2&uwo^;3r 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 literal 0 HcmV?d00001 diff --git a/testing/originalnodep/user.py b/testing/originalnodep/user.py new file mode 100644 index 00000000..ac7511e8 --- /dev/null +++ b/testing/originalnodep/user.py @@ -0,0 +1,16 @@ +from yaml import load, dump +try: + from yaml import CLoader as Loader, CDumper as Dumper +except ImportError: + from yaml import Loader, Dumper + +document = """ + a: 1 + b: + c: 3 + d: 4 +""" + + +def main(): + return dump(load(document, Loader=Loader), default_flow_style=None, Dumper=Dumper) diff --git a/testing/originalnodep/yaml/__init__.py b/testing/originalnodep/yaml/__init__.py new file mode 100644 index 00000000..82493619 --- /dev/null +++ b/testing/originalnodep/yaml/__init__.py @@ -0,0 +1,390 @@ + +from .error import * + +from .tokens import * +from .events import * +from .nodes import * + +from .loader import * +from .dumper import * + +__version__ = '6.0.1' +try: + from .cyaml import * + __with_libyaml__ = True +except ImportError: + __with_libyaml__ = False + +import io + +#------------------------------------------------------------------------------ +# XXX "Warnings control" is now deprecated. Leaving in the API function to not +# break code that uses it. +#------------------------------------------------------------------------------ +def warnings(settings=None): + if settings is None: + return {} + +#------------------------------------------------------------------------------ +def scan(stream, Loader=Loader): + """ + Scan a YAML stream and produce scanning tokens. + """ + loader = Loader(stream) + try: + while loader.check_token(): + yield loader.get_token() + finally: + loader.dispose() + +def parse(stream, Loader=Loader): + """ + Parse a YAML stream and produce parsing events. + """ + loader = Loader(stream) + try: + while loader.check_event(): + yield loader.get_event() + finally: + loader.dispose() + +def compose(stream, Loader=Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding representation tree. + """ + loader = Loader(stream) + try: + return loader.get_single_node() + finally: + loader.dispose() + +def compose_all(stream, Loader=Loader): + """ + Parse all YAML documents in a stream + and produce corresponding representation trees. + """ + loader = Loader(stream) + try: + while loader.check_node(): + yield loader.get_node() + finally: + loader.dispose() + +def load(stream, Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + """ + loader = Loader(stream) + try: + return loader.get_single_data() + finally: + loader.dispose() + +def load_all(stream, Loader): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + """ + loader = Loader(stream) + try: + while loader.check_data(): + yield loader.get_data() + finally: + loader.dispose() + +def full_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load(stream, FullLoader) + +def full_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load_all(stream, FullLoader) + +def safe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load(stream, SafeLoader) + +def safe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load_all(stream, SafeLoader) + +def unsafe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load(stream, UnsafeLoader) + +def unsafe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load_all(stream, UnsafeLoader) + +def emit(events, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + """ + Emit YAML parsing events into a stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + stream = io.StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + try: + for event in events: + dumper.emit(event) + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize_all(nodes, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + """ + Serialize a sequence of representation trees into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end) + try: + dumper.open() + for node in nodes: + dumper.serialize(node) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize(node, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a representation tree into a YAML stream. + If stream is None, return the produced string instead. + """ + return serialize_all([node], stream, Dumper=Dumper, **kwds) + +def dump_all(documents, stream=None, Dumper=Dumper, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + """ + Serialize a sequence of Python objects into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, default_style=default_style, + default_flow_style=default_flow_style, + canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys) + try: + dumper.open() + for data in documents: + dumper.represent(data) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def dump(data, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a Python object into a YAML stream. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=Dumper, **kwds) + +def safe_dump_all(documents, stream=None, **kwds): + """ + Serialize a sequence of Python objects into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all(documents, stream, Dumper=SafeDumper, **kwds) + +def safe_dump(data, stream=None, **kwds): + """ + Serialize a Python object into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=SafeDumper, **kwds) + +def add_implicit_resolver(tag, regexp, first=None, + Loader=None, Dumper=Dumper): + """ + Add an implicit scalar detector. + If an implicit scalar value matches the given regexp, + the corresponding tag is assigned to the scalar. + first is a sequence of possible initial characters or None. + """ + if Loader is None: + loader.Loader.add_implicit_resolver(tag, regexp, first) + loader.FullLoader.add_implicit_resolver(tag, regexp, first) + loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first) + else: + Loader.add_implicit_resolver(tag, regexp, first) + Dumper.add_implicit_resolver(tag, regexp, first) + +def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper): + """ + Add a path based resolver for the given tag. + A path is a list of keys that forms a path + to a node in the representation tree. + Keys can be string values, integers, or None. + """ + if Loader is None: + loader.Loader.add_path_resolver(tag, path, kind) + loader.FullLoader.add_path_resolver(tag, path, kind) + loader.UnsafeLoader.add_path_resolver(tag, path, kind) + else: + Loader.add_path_resolver(tag, path, kind) + Dumper.add_path_resolver(tag, path, kind) + +def add_constructor(tag, constructor, Loader=None): + """ + Add a constructor for the given tag. + Constructor is a function that accepts a Loader instance + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_constructor(tag, constructor) + loader.FullLoader.add_constructor(tag, constructor) + loader.UnsafeLoader.add_constructor(tag, constructor) + else: + Loader.add_constructor(tag, constructor) + +def add_multi_constructor(tag_prefix, multi_constructor, Loader=None): + """ + Add a multi-constructor for the given tag prefix. + Multi-constructor is called for a node if its tag starts with tag_prefix. + Multi-constructor accepts a Loader instance, a tag suffix, + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_multi_constructor(tag_prefix, multi_constructor) + loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor) + loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor) + else: + Loader.add_multi_constructor(tag_prefix, multi_constructor) + +def add_representer(data_type, representer, Dumper=Dumper): + """ + Add a representer for the given type. + Representer is a function accepting a Dumper instance + and an instance of the given data type + and producing the corresponding representation node. + """ + Dumper.add_representer(data_type, representer) + +def add_multi_representer(data_type, multi_representer, Dumper=Dumper): + """ + Add a representer for the given type. + Multi-representer is a function accepting a Dumper instance + and an instance of the given data type or subtype + and producing the corresponding representation node. + """ + Dumper.add_multi_representer(data_type, multi_representer) + +class YAMLObjectMetaclass(type): + """ + The metaclass for YAMLObject. + """ + def __init__(cls, name, bases, kwds): + super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) + if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: + if isinstance(cls.yaml_loader, list): + for loader in cls.yaml_loader: + loader.add_constructor(cls.yaml_tag, cls.from_yaml) + else: + cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) + + cls.yaml_dumper.add_representer(cls, cls.to_yaml) + +class YAMLObject(metaclass=YAMLObjectMetaclass): + """ + An object that can dump itself to a YAML stream + and load itself from a YAML stream. + """ + + __slots__ = () # no direct instantiation, so allow immutable subclasses + + yaml_loader = [Loader, FullLoader, UnsafeLoader] + yaml_dumper = Dumper + + yaml_tag = None + yaml_flow_style = None + + @classmethod + def from_yaml(cls, loader, node): + """ + Convert a representation node to a Python object. + """ + return loader.construct_yaml_object(node, cls) + + @classmethod + def to_yaml(cls, dumper, data): + """ + Convert a Python object to a representation node. + """ + return dumper.represent_yaml_object(cls.yaml_tag, data, cls, + flow_style=cls.yaml_flow_style) + diff --git a/testing/originalnodep/yaml/composer.py b/testing/originalnodep/yaml/composer.py new file mode 100644 index 00000000..6d15cb40 --- /dev/null +++ b/testing/originalnodep/yaml/composer.py @@ -0,0 +1,139 @@ + +__all__ = ['Composer', 'ComposerError'] + +from .error import MarkedYAMLError +from .events import * +from .nodes import * + +class ComposerError(MarkedYAMLError): + pass + +class Composer: + + def __init__(self): + self.anchors = {} + + def check_node(self): + # Drop the STREAM-START event. + if self.check_event(StreamStartEvent): + self.get_event() + + # If there are more documents available? + return not self.check_event(StreamEndEvent) + + def get_node(self): + # Get the root node of the next document. + if not self.check_event(StreamEndEvent): + return self.compose_document() + + def get_single_node(self): + # Drop the STREAM-START event. + self.get_event() + + # Compose a document if the stream is not empty. + document = None + if not self.check_event(StreamEndEvent): + document = self.compose_document() + + # Ensure that the stream contains no more documents. + if not self.check_event(StreamEndEvent): + event = self.get_event() + raise ComposerError("expected a single document in the stream", + document.start_mark, "but found another document", + event.start_mark) + + # Drop the STREAM-END event. + self.get_event() + + return document + + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + + # Compose the root node. + node = self.compose_node(None, None) + + # Drop the DOCUMENT-END event. + self.get_event() + + self.anchors = {} + return node + + def compose_node(self, parent, index): + if self.check_event(AliasEvent): + event = self.get_event() + anchor = event.anchor + if anchor not in self.anchors: + raise ComposerError(None, None, "found undefined alias %r" + % anchor, event.start_mark) + return self.anchors[anchor] + event = self.peek_event() + anchor = event.anchor + if anchor is not None: + if anchor in self.anchors: + raise ComposerError("found duplicate anchor %r; first occurrence" + % anchor, self.anchors[anchor].start_mark, + "second occurrence", event.start_mark) + self.descend_resolver(parent, index) + if self.check_event(ScalarEvent): + node = self.compose_scalar_node(anchor) + elif self.check_event(SequenceStartEvent): + node = self.compose_sequence_node(anchor) + elif self.check_event(MappingStartEvent): + node = self.compose_mapping_node(anchor) + self.ascend_resolver() + return node + + def compose_scalar_node(self, anchor): + event = self.get_event() + tag = event.tag + if tag is None or tag == '!': + tag = self.resolve(ScalarNode, event.value, event.implicit) + node = ScalarNode(tag, event.value, + event.start_mark, event.end_mark, style=event.style) + if anchor is not None: + self.anchors[anchor] = node + return node + + def compose_sequence_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(SequenceNode, None, start_event.implicit) + node = SequenceNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + index = 0 + while not self.check_event(SequenceEndEvent): + node.value.append(self.compose_node(node, index)) + index += 1 + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + + def compose_mapping_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(MappingNode, None, start_event.implicit) + node = MappingNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + while not self.check_event(MappingEndEvent): + #key_event = self.peek_event() + item_key = self.compose_node(node, None) + #if item_key in node.value: + # raise ComposerError("while composing a mapping", start_event.start_mark, + # "found duplicate key", key_event.start_mark) + item_value = self.compose_node(node, item_key) + #node.value[item_key] = item_value + node.value.append((item_key, item_value)) + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + diff --git a/testing/originalnodep/yaml/constructor.py b/testing/originalnodep/yaml/constructor.py new file mode 100644 index 00000000..619acd30 --- /dev/null +++ b/testing/originalnodep/yaml/constructor.py @@ -0,0 +1,748 @@ + +__all__ = [ + 'BaseConstructor', + 'SafeConstructor', + 'FullConstructor', + 'UnsafeConstructor', + 'Constructor', + 'ConstructorError' +] + +from .error import * +from .nodes import * + +import collections.abc, datetime, base64, binascii, re, sys, types + +class ConstructorError(MarkedYAMLError): + pass + +class BaseConstructor: + + yaml_constructors = {} + yaml_multi_constructors = {} + + def __init__(self): + self.constructed_objects = {} + self.recursive_objects = {} + self.state_generators = [] + self.deep_construct = False + + def check_data(self): + # If there are more documents available? + return self.check_node() + + def check_state_key(self, key): + """Block special attributes/methods from being set in a newly created + object, to prevent user-controlled methods from being called during + deserialization""" + if self.get_state_keys_blacklist_regexp().match(key): + raise ConstructorError(None, None, + "blacklisted key '%s' in instance state found" % (key,), None) + + def get_data(self): + # Construct and return the next document. + if self.check_node(): + return self.construct_document(self.get_node()) + + def get_single_data(self): + # Ensure that the stream contains a single document and construct it. + node = self.get_single_node() + if node is not None: + return self.construct_document(node) + return None + + def construct_document(self, node): + data = self.construct_object(node) + while self.state_generators: + state_generators = self.state_generators + self.state_generators = [] + for generator in state_generators: + for dummy in generator: + pass + self.constructed_objects = {} + self.recursive_objects = {} + self.deep_construct = False + return data + + def construct_object(self, node, deep=False): + if node in self.constructed_objects: + return self.constructed_objects[node] + if deep: + old_deep = self.deep_construct + self.deep_construct = True + if node in self.recursive_objects: + raise ConstructorError(None, None, + "found unconstructable recursive node", node.start_mark) + self.recursive_objects[node] = None + constructor = None + tag_suffix = None + if node.tag in self.yaml_constructors: + constructor = self.yaml_constructors[node.tag] + else: + for tag_prefix in self.yaml_multi_constructors: + if tag_prefix is not None and node.tag.startswith(tag_prefix): + tag_suffix = node.tag[len(tag_prefix):] + constructor = self.yaml_multi_constructors[tag_prefix] + break + else: + if None in self.yaml_multi_constructors: + tag_suffix = node.tag + constructor = self.yaml_multi_constructors[None] + elif None in self.yaml_constructors: + constructor = self.yaml_constructors[None] + elif isinstance(node, ScalarNode): + constructor = self.__class__.construct_scalar + elif isinstance(node, SequenceNode): + constructor = self.__class__.construct_sequence + elif isinstance(node, MappingNode): + constructor = self.__class__.construct_mapping + if tag_suffix is None: + data = constructor(self, node) + else: + data = constructor(self, tag_suffix, node) + if isinstance(data, types.GeneratorType): + generator = data + data = next(generator) + if self.deep_construct: + for dummy in generator: + pass + else: + self.state_generators.append(generator) + self.constructed_objects[node] = data + del self.recursive_objects[node] + if deep: + self.deep_construct = old_deep + return data + + def construct_scalar(self, node): + if not isinstance(node, ScalarNode): + raise ConstructorError(None, None, + "expected a scalar node, but found %s" % node.id, + node.start_mark) + return node.value + + def construct_sequence(self, node, deep=False): + if not isinstance(node, SequenceNode): + raise ConstructorError(None, None, + "expected a sequence node, but found %s" % node.id, + node.start_mark) + return [self.construct_object(child, deep=deep) + for child in node.value] + + def construct_mapping(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + mapping = {} + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, collections.abc.Hashable): + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unhashable key", key_node.start_mark) + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + + def construct_pairs(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + pairs = [] + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + value = self.construct_object(value_node, deep=deep) + pairs.append((key, value)) + return pairs + + @classmethod + def add_constructor(cls, tag, constructor): + if not 'yaml_constructors' in cls.__dict__: + cls.yaml_constructors = cls.yaml_constructors.copy() + cls.yaml_constructors[tag] = constructor + + @classmethod + def add_multi_constructor(cls, tag_prefix, multi_constructor): + if not 'yaml_multi_constructors' in cls.__dict__: + cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() + cls.yaml_multi_constructors[tag_prefix] = multi_constructor + +class SafeConstructor(BaseConstructor): + + def construct_scalar(self, node): + if isinstance(node, MappingNode): + for key_node, value_node in node.value: + if key_node.tag == 'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return super().construct_scalar(node) + + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == 'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError("while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark) + elif key_node.tag == 'tag:yaml.org,2002:value': + key_node.tag = 'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value + + def construct_mapping(self, node, deep=False): + if isinstance(node, MappingNode): + self.flatten_mapping(node) + return super().construct_mapping(node, deep=deep) + + def construct_yaml_null(self, node): + self.construct_scalar(node) + return None + + bool_values = { + 'yes': True, + 'no': False, + 'true': True, + 'false': False, + 'on': True, + 'off': False, + } + + def construct_yaml_bool(self, node): + value = self.construct_scalar(node) + return self.bool_values[value.lower()] + + def construct_yaml_int(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '') + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + elif value.startswith('0b'): + return sign*int(value[2:], 2) + elif value.startswith('0x'): + return sign*int(value[2:], 16) + elif value[0] == '0': + return sign*int(value, 8) + elif ':' in value: + digits = [int(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value) + + inf_value = 1e300 + while inf_value != inf_value*inf_value: + inf_value *= inf_value + nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). + + def construct_yaml_float(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '').lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '.inf': + return sign*self.inf_value + elif value == '.nan': + return self.nan_value + elif ':' in value: + digits = [float(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0.0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*float(value) + + def construct_yaml_binary(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + timestamp_regexp = re.compile( + r'''^(?P[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)?$''', re.X) + + def construct_yaml_timestamp(self, node): + value = self.construct_scalar(node) + match = self.timestamp_regexp.match(node.value) + values = match.groupdict() + year = int(values['year']) + month = int(values['month']) + day = int(values['day']) + if not values['hour']: + return datetime.date(year, month, day) + hour = int(values['hour']) + minute = int(values['minute']) + second = int(values['second']) + fraction = 0 + tzinfo = None + if values['fraction']: + fraction = values['fraction'][:6] + while len(fraction) < 6: + fraction += '0' + fraction = int(fraction) + if values['tz_sign']: + tz_hour = int(values['tz_hour']) + tz_minute = int(values['tz_minute'] or 0) + delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) + if values['tz_sign'] == '-': + delta = -delta + tzinfo = datetime.timezone(delta) + elif values['tz']: + tzinfo = datetime.timezone.utc + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo) + + def construct_yaml_omap(self, node): + # Note: we do not check for duplicate keys, because it's too + # CPU-expensive. + omap = [] + yield omap + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + omap.append((key, value)) + + def construct_yaml_pairs(self, node): + # Note: the same code as `construct_yaml_omap`. + pairs = [] + yield pairs + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + pairs.append((key, value)) + + def construct_yaml_set(self, node): + data = set() + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_str(self, node): + return self.construct_scalar(node) + + def construct_yaml_seq(self, node): + data = [] + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = {} + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_object(self, node, cls): + data = cls.__new__(cls) + yield data + if hasattr(data, '__setstate__'): + state = self.construct_mapping(node, deep=True) + data.__setstate__(state) + else: + state = self.construct_mapping(node) + data.__dict__.update(state) + + def construct_undefined(self, node): + raise ConstructorError(None, None, + "could not determine a constructor for the tag %r" % node.tag, + node.start_mark) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:null', + SafeConstructor.construct_yaml_null) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:bool', + SafeConstructor.construct_yaml_bool) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:int', + SafeConstructor.construct_yaml_int) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:float', + SafeConstructor.construct_yaml_float) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:binary', + SafeConstructor.construct_yaml_binary) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:timestamp', + SafeConstructor.construct_yaml_timestamp) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:omap', + SafeConstructor.construct_yaml_omap) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:pairs', + SafeConstructor.construct_yaml_pairs) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:set', + SafeConstructor.construct_yaml_set) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:str', + SafeConstructor.construct_yaml_str) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:seq', + SafeConstructor.construct_yaml_seq) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:map', + SafeConstructor.construct_yaml_map) + +SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + +class FullConstructor(SafeConstructor): + # 'extend' is blacklisted because it is used by + # construct_python_object_apply to add `listitems` to a newly generate + # python instance + def get_state_keys_blacklist(self): + return ['^extend$', '^__.*__$'] + + def get_state_keys_blacklist_regexp(self): + if not hasattr(self, 'state_keys_blacklist_regexp'): + self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')') + return self.state_keys_blacklist_regexp + + def construct_python_str(self, node): + return self.construct_scalar(node) + + def construct_python_unicode(self, node): + return self.construct_scalar(node) + + def construct_python_bytes(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + def construct_python_long(self, node): + return self.construct_yaml_int(node) + + def construct_python_complex(self, node): + return complex(self.construct_scalar(node)) + + def construct_python_tuple(self, node): + return tuple(self.construct_sequence(node)) + + def find_python_module(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python module", mark, + "expected non-empty name appended to the tag", mark) + if unsafe: + try: + __import__(name) + except ImportError as exc: + raise ConstructorError("while constructing a Python module", mark, + "cannot find module %r (%s)" % (name, exc), mark) + if name not in sys.modules: + raise ConstructorError("while constructing a Python module", mark, + "module %r is not imported" % name, mark) + return sys.modules[name] + + def find_python_name(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python object", mark, + "expected non-empty name appended to the tag", mark) + if '.' in name: + module_name, object_name = name.rsplit('.', 1) + else: + module_name = 'builtins' + object_name = name + if unsafe: + try: + __import__(module_name) + except ImportError as exc: + raise ConstructorError("while constructing a Python object", mark, + "cannot find module %r (%s)" % (module_name, exc), mark) + if module_name not in sys.modules: + raise ConstructorError("while constructing a Python object", mark, + "module %r is not imported" % module_name, mark) + module = sys.modules[module_name] + if not hasattr(module, object_name): + raise ConstructorError("while constructing a Python object", mark, + "cannot find %r in the module %r" + % (object_name, module.__name__), mark) + return getattr(module, object_name) + + def construct_python_name(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python name", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_name(suffix, node.start_mark) + + def construct_python_module(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python module", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_module(suffix, node.start_mark) + + def make_python_instance(self, suffix, node, + args=None, kwds=None, newobj=False, unsafe=False): + if not args: + args = [] + if not kwds: + kwds = {} + cls = self.find_python_name(suffix, node.start_mark) + if not (unsafe or isinstance(cls, type)): + raise ConstructorError("while constructing a Python instance", node.start_mark, + "expected a class, but found %r" % type(cls), + node.start_mark) + if newobj and isinstance(cls, type): + return cls.__new__(cls, *args, **kwds) + else: + return cls(*args, **kwds) + + def set_python_instance_state(self, instance, state, unsafe=False): + if hasattr(instance, '__setstate__'): + instance.__setstate__(state) + else: + slotstate = {} + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + if hasattr(instance, '__dict__'): + if not unsafe and state: + for key in state.keys(): + self.check_state_key(key) + instance.__dict__.update(state) + elif state: + slotstate.update(state) + for key, value in slotstate.items(): + if not unsafe: + self.check_state_key(key) + setattr(instance, key, value) + + def construct_python_object(self, suffix, node): + # Format: + # !!python/object:module.name { ... state ... } + instance = self.make_python_instance(suffix, node, newobj=True) + yield instance + deep = hasattr(instance, '__setstate__') + state = self.construct_mapping(node, deep=deep) + self.set_python_instance_state(instance, state) + + def construct_python_object_apply(self, suffix, node, newobj=False): + # Format: + # !!python/object/apply # (or !!python/object/new) + # args: [ ... arguments ... ] + # kwds: { ... keywords ... } + # state: ... state ... + # listitems: [ ... listitems ... ] + # dictitems: { ... dictitems ... } + # or short format: + # !!python/object/apply [ ... arguments ... ] + # The difference between !!python/object/apply and !!python/object/new + # is how an object is created, check make_python_instance for details. + if isinstance(node, SequenceNode): + args = self.construct_sequence(node, deep=True) + kwds = {} + state = {} + listitems = [] + dictitems = {} + else: + value = self.construct_mapping(node, deep=True) + args = value.get('args', []) + kwds = value.get('kwds', {}) + state = value.get('state', {}) + listitems = value.get('listitems', []) + dictitems = value.get('dictitems', {}) + instance = self.make_python_instance(suffix, node, args, kwds, newobj) + if state: + self.set_python_instance_state(instance, state) + if listitems: + instance.extend(listitems) + if dictitems: + for key in dictitems: + instance[key] = dictitems[key] + return instance + + def construct_python_object_new(self, suffix, node): + return self.construct_python_object_apply(suffix, node, newobj=True) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/none', + FullConstructor.construct_yaml_null) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bool', + FullConstructor.construct_yaml_bool) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/str', + FullConstructor.construct_python_str) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/unicode', + FullConstructor.construct_python_unicode) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bytes', + FullConstructor.construct_python_bytes) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/int', + FullConstructor.construct_yaml_int) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/long', + FullConstructor.construct_python_long) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/float', + FullConstructor.construct_yaml_float) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/complex', + FullConstructor.construct_python_complex) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/list', + FullConstructor.construct_yaml_seq) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/tuple', + FullConstructor.construct_python_tuple) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/dict', + FullConstructor.construct_yaml_map) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/name:', + FullConstructor.construct_python_name) + +class UnsafeConstructor(FullConstructor): + + def find_python_module(self, name, mark): + return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True) + + def find_python_name(self, name, mark): + return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True) + + def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False): + return super(UnsafeConstructor, self).make_python_instance( + suffix, node, args, kwds, newobj, unsafe=True) + + def set_python_instance_state(self, instance, state): + return super(UnsafeConstructor, self).set_python_instance_state( + instance, state, unsafe=True) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/module:', + UnsafeConstructor.construct_python_module) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object:', + UnsafeConstructor.construct_python_object) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/new:', + UnsafeConstructor.construct_python_object_new) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/apply:', + UnsafeConstructor.construct_python_object_apply) + +# Constructor is same as UnsafeConstructor. Need to leave this in place in case +# people have extended it directly. +class Constructor(UnsafeConstructor): + pass diff --git a/testing/originalnodep/yaml/cyaml.py b/testing/originalnodep/yaml/cyaml.py new file mode 100644 index 00000000..0c213458 --- /dev/null +++ b/testing/originalnodep/yaml/cyaml.py @@ -0,0 +1,101 @@ + +__all__ = [ + 'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader', + 'CBaseDumper', 'CSafeDumper', 'CDumper' +] + +from yaml._yaml import CParser, CEmitter + +from .constructor import * + +from .serializer import * +from .representer import * + +from .resolver import * + +class CBaseLoader(CParser, BaseConstructor, BaseResolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class CSafeLoader(CParser, SafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class CFullLoader(CParser, FullConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class CUnsafeLoader(CParser, UnsafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + UnsafeConstructor.__init__(self) + Resolver.__init__(self) + +class CLoader(CParser, Constructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + Constructor.__init__(self) + Resolver.__init__(self) + +class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CSafeDumper(CEmitter, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CDumper(CEmitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/testing/originalnodep/yaml/dumper.py b/testing/originalnodep/yaml/dumper.py new file mode 100644 index 00000000..6aadba55 --- /dev/null +++ b/testing/originalnodep/yaml/dumper.py @@ -0,0 +1,62 @@ + +__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] + +from .emitter import * +from .serializer import * +from .representer import * +from .resolver import * + +class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class Dumper(Emitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/testing/originalnodep/yaml/emitter.py b/testing/originalnodep/yaml/emitter.py new file mode 100644 index 00000000..a664d011 --- /dev/null +++ b/testing/originalnodep/yaml/emitter.py @@ -0,0 +1,1137 @@ + +# Emitter expects events obeying the following grammar: +# stream ::= STREAM-START document* STREAM-END +# document ::= DOCUMENT-START node DOCUMENT-END +# node ::= SCALAR | sequence | mapping +# sequence ::= SEQUENCE-START node* SEQUENCE-END +# mapping ::= MAPPING-START (node node)* MAPPING-END + +__all__ = ['Emitter', 'EmitterError'] + +from .error import YAMLError +from .events import * + +class EmitterError(YAMLError): + pass + +class ScalarAnalysis: + def __init__(self, scalar, empty, multiline, + allow_flow_plain, allow_block_plain, + allow_single_quoted, allow_double_quoted, + allow_block): + self.scalar = scalar + self.empty = empty + self.multiline = multiline + self.allow_flow_plain = allow_flow_plain + self.allow_block_plain = allow_block_plain + self.allow_single_quoted = allow_single_quoted + self.allow_double_quoted = allow_double_quoted + self.allow_block = allow_block + +class Emitter: + + DEFAULT_TAG_PREFIXES = { + '!' : '!', + 'tag:yaml.org,2002:' : '!!', + } + + def __init__(self, stream, canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + + # The stream should have the methods `write` and possibly `flush`. + self.stream = stream + + # Encoding can be overridden by STREAM-START. + self.encoding = None + + # Emitter is a state machine with a stack of states to handle nested + # structures. + self.states = [] + self.state = self.expect_stream_start + + # Current event and the event queue. + self.events = [] + self.event = None + + # The current indentation level and the stack of previous indents. + self.indents = [] + self.indent = None + + # Flow level. + self.flow_level = 0 + + # Contexts. + self.root_context = False + self.sequence_context = False + self.mapping_context = False + self.simple_key_context = False + + # Characteristics of the last emitted character: + # - current position. + # - is it a whitespace? + # - is it an indention character + # (indentation space, '-', '?', or ':')? + self.line = 0 + self.column = 0 + self.whitespace = True + self.indention = True + + # Whether the document requires an explicit document indicator + self.open_ended = False + + # Formatting details. + self.canonical = canonical + self.allow_unicode = allow_unicode + self.best_indent = 2 + if indent and 1 < indent < 10: + self.best_indent = indent + self.best_width = 80 + if width and width > self.best_indent*2: + self.best_width = width + self.best_line_break = '\n' + if line_break in ['\r', '\n', '\r\n']: + self.best_line_break = line_break + + # Tag prefixes. + self.tag_prefixes = None + + # Prepared anchor and tag. + self.prepared_anchor = None + self.prepared_tag = None + + # Scalar analysis and style. + self.analysis = None + self.style = None + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def emit(self, event): + self.events.append(event) + while not self.need_more_events(): + self.event = self.events.pop(0) + self.state() + self.event = None + + # In some cases, we wait for a few next events before emitting. + + def need_more_events(self): + if not self.events: + return True + event = self.events[0] + if isinstance(event, DocumentStartEvent): + return self.need_events(1) + elif isinstance(event, SequenceStartEvent): + return self.need_events(2) + elif isinstance(event, MappingStartEvent): + return self.need_events(3) + else: + return False + + def need_events(self, count): + level = 0 + for event in self.events[1:]: + if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): + level += 1 + elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): + level -= 1 + elif isinstance(event, StreamEndEvent): + level = -1 + if level < 0: + return False + return (len(self.events) < count+1) + + def increase_indent(self, flow=False, indentless=False): + self.indents.append(self.indent) + if self.indent is None: + if flow: + self.indent = self.best_indent + else: + self.indent = 0 + elif not indentless: + self.indent += self.best_indent + + # States. + + # Stream handlers. + + def expect_stream_start(self): + if isinstance(self.event, StreamStartEvent): + if self.event.encoding and not hasattr(self.stream, 'encoding'): + self.encoding = self.event.encoding + self.write_stream_start() + self.state = self.expect_first_document_start + else: + raise EmitterError("expected StreamStartEvent, but got %s" + % self.event) + + def expect_nothing(self): + raise EmitterError("expected nothing, but got %s" % self.event) + + # Document handlers. + + def expect_first_document_start(self): + return self.expect_document_start(first=True) + + def expect_document_start(self, first=False): + if isinstance(self.event, DocumentStartEvent): + if (self.event.version or self.event.tags) and self.open_ended: + self.write_indicator('...', True) + self.write_indent() + if self.event.version: + version_text = self.prepare_version(self.event.version) + self.write_version_directive(version_text) + self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() + if self.event.tags: + handles = sorted(self.event.tags.keys()) + for handle in handles: + prefix = self.event.tags[handle] + self.tag_prefixes[prefix] = handle + handle_text = self.prepare_tag_handle(handle) + prefix_text = self.prepare_tag_prefix(prefix) + self.write_tag_directive(handle_text, prefix_text) + implicit = (first and not self.event.explicit and not self.canonical + and not self.event.version and not self.event.tags + and not self.check_empty_document()) + if not implicit: + self.write_indent() + self.write_indicator('---', True) + if self.canonical: + self.write_indent() + self.state = self.expect_document_root + elif isinstance(self.event, StreamEndEvent): + if self.open_ended: + self.write_indicator('...', True) + self.write_indent() + self.write_stream_end() + self.state = self.expect_nothing + else: + raise EmitterError("expected DocumentStartEvent, but got %s" + % self.event) + + def expect_document_end(self): + if isinstance(self.event, DocumentEndEvent): + self.write_indent() + if self.event.explicit: + self.write_indicator('...', True) + self.write_indent() + self.flush_stream() + self.state = self.expect_document_start + else: + raise EmitterError("expected DocumentEndEvent, but got %s" + % self.event) + + def expect_document_root(self): + self.states.append(self.expect_document_end) + self.expect_node(root=True) + + # Node handlers. + + def expect_node(self, root=False, sequence=False, mapping=False, + simple_key=False): + self.root_context = root + self.sequence_context = sequence + self.mapping_context = mapping + self.simple_key_context = simple_key + if isinstance(self.event, AliasEvent): + self.expect_alias() + elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): + self.process_anchor('&') + self.process_tag() + if isinstance(self.event, ScalarEvent): + self.expect_scalar() + elif isinstance(self.event, SequenceStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_sequence(): + self.expect_flow_sequence() + else: + self.expect_block_sequence() + elif isinstance(self.event, MappingStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_mapping(): + self.expect_flow_mapping() + else: + self.expect_block_mapping() + else: + raise EmitterError("expected NodeEvent, but got %s" % self.event) + + def expect_alias(self): + if self.event.anchor is None: + raise EmitterError("anchor is not specified for alias") + self.process_anchor('*') + self.state = self.states.pop() + + def expect_scalar(self): + self.increase_indent(flow=True) + self.process_scalar() + self.indent = self.indents.pop() + self.state = self.states.pop() + + # Flow sequence handlers. + + def expect_flow_sequence(self): + self.write_indicator('[', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_sequence_item + + def expect_first_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator(']', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + def expect_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator(']', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + # Flow mapping handlers. + + def expect_flow_mapping(self): + self.write_indicator('{', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_mapping_key + + def expect_first_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator('}', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator('}', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + def expect_flow_mapping_value(self): + if self.canonical or self.column > self.best_width: + self.write_indent() + self.write_indicator(':', True) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + # Block sequence handlers. + + def expect_block_sequence(self): + indentless = (self.mapping_context and not self.indention) + self.increase_indent(flow=False, indentless=indentless) + self.state = self.expect_first_block_sequence_item + + def expect_first_block_sequence_item(self): + return self.expect_block_sequence_item(first=True) + + def expect_block_sequence_item(self, first=False): + if not first and isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + self.write_indicator('-', True, indention=True) + self.states.append(self.expect_block_sequence_item) + self.expect_node(sequence=True) + + # Block mapping handlers. + + def expect_block_mapping(self): + self.increase_indent(flow=False) + self.state = self.expect_first_block_mapping_key + + def expect_first_block_mapping_key(self): + return self.expect_block_mapping_key(first=True) + + def expect_block_mapping_key(self, first=False): + if not first and isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + if self.check_simple_key(): + self.states.append(self.expect_block_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True, indention=True) + self.states.append(self.expect_block_mapping_value) + self.expect_node(mapping=True) + + def expect_block_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + def expect_block_mapping_value(self): + self.write_indent() + self.write_indicator(':', True, indention=True) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + # Checkers. + + def check_empty_sequence(self): + return (isinstance(self.event, SequenceStartEvent) and self.events + and isinstance(self.events[0], SequenceEndEvent)) + + def check_empty_mapping(self): + return (isinstance(self.event, MappingStartEvent) and self.events + and isinstance(self.events[0], MappingEndEvent)) + + def check_empty_document(self): + if not isinstance(self.event, DocumentStartEvent) or not self.events: + return False + event = self.events[0] + return (isinstance(event, ScalarEvent) and event.anchor is None + and event.tag is None and event.implicit and event.value == '') + + def check_simple_key(self): + length = 0 + if isinstance(self.event, NodeEvent) and self.event.anchor is not None: + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + length += len(self.prepared_anchor) + if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ + and self.event.tag is not None: + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(self.event.tag) + length += len(self.prepared_tag) + if isinstance(self.event, ScalarEvent): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + length += len(self.analysis.scalar) + return (length < 128 and (isinstance(self.event, AliasEvent) + or (isinstance(self.event, ScalarEvent) + and not self.analysis.empty and not self.analysis.multiline) + or self.check_empty_sequence() or self.check_empty_mapping())) + + # Anchor, Tag, and Scalar processors. + + def process_anchor(self, indicator): + if self.event.anchor is None: + self.prepared_anchor = None + return + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + if self.prepared_anchor: + self.write_indicator(indicator+self.prepared_anchor, True) + self.prepared_anchor = None + + def process_tag(self): + tag = self.event.tag + if isinstance(self.event, ScalarEvent): + if self.style is None: + self.style = self.choose_scalar_style() + if ((not self.canonical or tag is None) and + ((self.style == '' and self.event.implicit[0]) + or (self.style != '' and self.event.implicit[1]))): + self.prepared_tag = None + return + if self.event.implicit[0] and tag is None: + tag = '!' + self.prepared_tag = None + else: + if (not self.canonical or tag is None) and self.event.implicit: + self.prepared_tag = None + return + if tag is None: + raise EmitterError("tag is not specified") + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(tag) + if self.prepared_tag: + self.write_indicator(self.prepared_tag, True) + self.prepared_tag = None + + def choose_scalar_style(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.event.style == '"' or self.canonical: + return '"' + if not self.event.style and self.event.implicit[0]: + if (not (self.simple_key_context and + (self.analysis.empty or self.analysis.multiline)) + and (self.flow_level and self.analysis.allow_flow_plain + or (not self.flow_level and self.analysis.allow_block_plain))): + return '' + if self.event.style and self.event.style in '|>': + if (not self.flow_level and not self.simple_key_context + and self.analysis.allow_block): + return self.event.style + if not self.event.style or self.event.style == '\'': + if (self.analysis.allow_single_quoted and + not (self.simple_key_context and self.analysis.multiline)): + return '\'' + return '"' + + def process_scalar(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.style is None: + self.style = self.choose_scalar_style() + split = (not self.simple_key_context) + #if self.analysis.multiline and split \ + # and (not self.style or self.style in '\'\"'): + # self.write_indent() + if self.style == '"': + self.write_double_quoted(self.analysis.scalar, split) + elif self.style == '\'': + self.write_single_quoted(self.analysis.scalar, split) + elif self.style == '>': + self.write_folded(self.analysis.scalar) + elif self.style == '|': + self.write_literal(self.analysis.scalar) + else: + self.write_plain(self.analysis.scalar, split) + self.analysis = None + self.style = None + + # Analyzers. + + def prepare_version(self, version): + major, minor = version + if major != 1: + raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) + return '%d.%d' % (major, minor) + + def prepare_tag_handle(self, handle): + if not handle: + raise EmitterError("tag handle must not be empty") + if handle[0] != '!' or handle[-1] != '!': + raise EmitterError("tag handle must start and end with '!': %r" % handle) + for ch in handle[1:-1]: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the tag handle: %r" + % (ch, handle)) + return handle + + def prepare_tag_prefix(self, prefix): + if not prefix: + raise EmitterError("tag prefix must not be empty") + chunks = [] + start = end = 0 + if prefix[0] == '!': + end = 1 + while end < len(prefix): + ch = prefix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?!:@&=+$,_.~*\'()[]': + end += 1 + else: + if start < end: + chunks.append(prefix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ord(ch)) + if start < end: + chunks.append(prefix[start:end]) + return ''.join(chunks) + + def prepare_tag(self, tag): + if not tag: + raise EmitterError("tag must not be empty") + if tag == '!': + return tag + handle = None + suffix = tag + prefixes = sorted(self.tag_prefixes.keys()) + for prefix in prefixes: + if tag.startswith(prefix) \ + and (prefix == '!' or len(prefix) < len(tag)): + handle = self.tag_prefixes[prefix] + suffix = tag[len(prefix):] + chunks = [] + start = end = 0 + while end < len(suffix): + ch = suffix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.~*\'()[]' \ + or (ch == '!' and handle != '!'): + end += 1 + else: + if start < end: + chunks.append(suffix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ch) + if start < end: + chunks.append(suffix[start:end]) + suffix_text = ''.join(chunks) + if handle: + return '%s%s' % (handle, suffix_text) + else: + return '!<%s>' % suffix_text + + def prepare_anchor(self, anchor): + if not anchor: + raise EmitterError("anchor must not be empty") + for ch in anchor: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the anchor: %r" + % (ch, anchor)) + return anchor + + def analyze_scalar(self, scalar): + + # Empty scalar is a special case. + if not scalar: + return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, + allow_flow_plain=False, allow_block_plain=True, + allow_single_quoted=True, allow_double_quoted=True, + allow_block=False) + + # Indicators and special characters. + block_indicators = False + flow_indicators = False + line_breaks = False + special_characters = False + + # Important whitespace combinations. + leading_space = False + leading_break = False + trailing_space = False + trailing_break = False + break_space = False + space_break = False + + # Check document indicators. + if scalar.startswith('---') or scalar.startswith('...'): + block_indicators = True + flow_indicators = True + + # First character or preceded by a whitespace. + preceded_by_whitespace = True + + # Last character or followed by a whitespace. + followed_by_whitespace = (len(scalar) == 1 or + scalar[1] in '\0 \t\r\n\x85\u2028\u2029') + + # The previous character is a space. + previous_space = False + + # The previous character is a break. + previous_break = False + + index = 0 + while index < len(scalar): + ch = scalar[index] + + # Check for indicators. + if index == 0: + # Leading indicators are special characters. + if ch in '#,[]{}&*!|>\'\"%@`': + flow_indicators = True + block_indicators = True + if ch in '?:': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '-' and followed_by_whitespace: + flow_indicators = True + block_indicators = True + else: + # Some indicators cannot appear within a scalar as well. + if ch in ',?[]{}': + flow_indicators = True + if ch == ':': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '#' and preceded_by_whitespace: + flow_indicators = True + block_indicators = True + + # Check for line breaks, special, and unicode characters. + if ch in '\n\x85\u2028\u2029': + line_breaks = True + if not (ch == '\n' or '\x20' <= ch <= '\x7E'): + if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD' + or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': + unicode_characters = True + if not self.allow_unicode: + special_characters = True + else: + special_characters = True + + # Detect important whitespace combinations. + if ch == ' ': + if index == 0: + leading_space = True + if index == len(scalar)-1: + trailing_space = True + if previous_break: + break_space = True + previous_space = True + previous_break = False + elif ch in '\n\x85\u2028\u2029': + if index == 0: + leading_break = True + if index == len(scalar)-1: + trailing_break = True + if previous_space: + space_break = True + previous_space = False + previous_break = True + else: + previous_space = False + previous_break = False + + # Prepare for the next character. + index += 1 + preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = (index+1 >= len(scalar) or + scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') + + # Let's decide what styles are allowed. + allow_flow_plain = True + allow_block_plain = True + allow_single_quoted = True + allow_double_quoted = True + allow_block = True + + # Leading and trailing whitespaces are bad for plain scalars. + if (leading_space or leading_break + or trailing_space or trailing_break): + allow_flow_plain = allow_block_plain = False + + # We do not permit trailing spaces for block scalars. + if trailing_space: + allow_block = False + + # Spaces at the beginning of a new line are only acceptable for block + # scalars. + if break_space: + allow_flow_plain = allow_block_plain = allow_single_quoted = False + + # Spaces followed by breaks, as well as special character are only + # allowed for double quoted scalars. + if space_break or special_characters: + allow_flow_plain = allow_block_plain = \ + allow_single_quoted = allow_block = False + + # Although the plain scalar writer supports breaks, we never emit + # multiline plain scalars. + if line_breaks: + allow_flow_plain = allow_block_plain = False + + # Flow indicators are forbidden for flow plain scalars. + if flow_indicators: + allow_flow_plain = False + + # Block indicators are forbidden for block plain scalars. + if block_indicators: + allow_block_plain = False + + return ScalarAnalysis(scalar=scalar, + empty=False, multiline=line_breaks, + allow_flow_plain=allow_flow_plain, + allow_block_plain=allow_block_plain, + allow_single_quoted=allow_single_quoted, + allow_double_quoted=allow_double_quoted, + allow_block=allow_block) + + # Writers. + + def flush_stream(self): + if hasattr(self.stream, 'flush'): + self.stream.flush() + + def write_stream_start(self): + # Write BOM if needed. + if self.encoding and self.encoding.startswith('utf-16'): + self.stream.write('\uFEFF'.encode(self.encoding)) + + def write_stream_end(self): + self.flush_stream() + + def write_indicator(self, indicator, need_whitespace, + whitespace=False, indention=False): + if self.whitespace or not need_whitespace: + data = indicator + else: + data = ' '+indicator + self.whitespace = whitespace + self.indention = self.indention and indention + self.column += len(data) + self.open_ended = False + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_indent(self): + indent = self.indent or 0 + if not self.indention or self.column > indent \ + or (self.column == indent and not self.whitespace): + self.write_line_break() + if self.column < indent: + self.whitespace = True + data = ' '*(indent-self.column) + self.column = indent + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_line_break(self, data=None): + if data is None: + data = self.best_line_break + self.whitespace = True + self.indention = True + self.line += 1 + self.column = 0 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_version_directive(self, version_text): + data = '%%YAML %s' % version_text + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + def write_tag_directive(self, handle_text, prefix_text): + data = '%%TAG %s %s' % (handle_text, prefix_text) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + # Scalar streams. + + def write_single_quoted(self, text, split=True): + self.write_indicator('\'', True) + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch is None or ch != ' ': + if start+1 == end and self.column > self.best_width and split \ + and start != 0 and end != len(text): + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'': + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch == '\'': + data = '\'\'' + self.column += 2 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + 1 + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + self.write_indicator('\'', False) + + ESCAPE_REPLACEMENTS = { + '\0': '0', + '\x07': 'a', + '\x08': 'b', + '\x09': 't', + '\x0A': 'n', + '\x0B': 'v', + '\x0C': 'f', + '\x0D': 'r', + '\x1B': 'e', + '\"': '\"', + '\\': '\\', + '\x85': 'N', + '\xA0': '_', + '\u2028': 'L', + '\u2029': 'P', + } + + def write_double_quoted(self, text, split=True): + self.write_indicator('"', True) + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ + or not ('\x20' <= ch <= '\x7E' + or (self.allow_unicode + and ('\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD'))): + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + if ch in self.ESCAPE_REPLACEMENTS: + data = '\\'+self.ESCAPE_REPLACEMENTS[ch] + elif ch <= '\xFF': + data = '\\x%02X' % ord(ch) + elif ch <= '\uFFFF': + data = '\\u%04X' % ord(ch) + else: + data = '\\U%08X' % ord(ch) + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end+1 + if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \ + and self.column+(end-start) > self.best_width and split: + data = text[start:end]+'\\' + if start < end: + start = end + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_indent() + self.whitespace = False + self.indention = False + if text[start] == ' ': + data = '\\' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + end += 1 + self.write_indicator('"', False) + + def determine_block_hints(self, text): + hints = '' + if text: + if text[0] in ' \n\x85\u2028\u2029': + hints += str(self.best_indent) + if text[-1] not in '\n\x85\u2028\u2029': + hints += '-' + elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029': + hints += '+' + return hints + + def write_folded(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('>'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + leading_space = True + spaces = False + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if not leading_space and ch is not None and ch != ' ' \ + and text[start] == '\n': + self.write_line_break() + leading_space = (ch == ' ') + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + elif spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width: + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + spaces = (ch == ' ') + end += 1 + + def write_literal(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('|'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + else: + if ch is None or ch in '\n\x85\u2028\u2029': + data = text[start:end] + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + + def write_plain(self, text, split=True): + if self.root_context: + self.open_ended = True + if not text: + return + if not self.whitespace: + data = ' ' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.whitespace = False + self.indention = False + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width and split: + self.write_indent() + self.whitespace = False + self.indention = False + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + self.whitespace = False + self.indention = False + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 diff --git a/testing/originalnodep/yaml/error.py b/testing/originalnodep/yaml/error.py new file mode 100644 index 00000000..b796b4dc --- /dev/null +++ b/testing/originalnodep/yaml/error.py @@ -0,0 +1,75 @@ + +__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError'] + +class Mark: + + def __init__(self, name, index, line, column, buffer, pointer): + self.name = name + self.index = index + self.line = line + self.column = column + self.buffer = buffer + self.pointer = pointer + + def get_snippet(self, indent=4, max_length=75): + if self.buffer is None: + return None + head = '' + start = self.pointer + while start > 0 and self.buffer[start-1] not in '\0\r\n\x85\u2028\u2029': + start -= 1 + if self.pointer-start > max_length/2-1: + head = ' ... ' + start += 5 + break + tail = '' + end = self.pointer + while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029': + end += 1 + if end-self.pointer > max_length/2-1: + tail = ' ... ' + end -= 5 + break + snippet = self.buffer[start:end] + return ' '*indent + head + snippet + tail + '\n' \ + + ' '*(indent+self.pointer-start+len(head)) + '^' + + def __str__(self): + snippet = self.get_snippet() + where = " in \"%s\", line %d, column %d" \ + % (self.name, self.line+1, self.column+1) + if snippet is not None: + where += ":\n"+snippet + return where + +class YAMLError(Exception): + pass + +class MarkedYAMLError(YAMLError): + + def __init__(self, context=None, context_mark=None, + problem=None, problem_mark=None, note=None): + self.context = context + self.context_mark = context_mark + self.problem = problem + self.problem_mark = problem_mark + self.note = note + + def __str__(self): + lines = [] + if self.context is not None: + lines.append(self.context) + if self.context_mark is not None \ + and (self.problem is None or self.problem_mark is None + or self.context_mark.name != self.problem_mark.name + or self.context_mark.line != self.problem_mark.line + or self.context_mark.column != self.problem_mark.column): + lines.append(str(self.context_mark)) + if self.problem is not None: + lines.append(self.problem) + if self.problem_mark is not None: + lines.append(str(self.problem_mark)) + if self.note is not None: + lines.append(self.note) + return '\n'.join(lines) + diff --git a/testing/originalnodep/yaml/events.py b/testing/originalnodep/yaml/events.py new file mode 100644 index 00000000..f79ad389 --- /dev/null +++ b/testing/originalnodep/yaml/events.py @@ -0,0 +1,86 @@ + +# Abstract classes. + +class Event(object): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] + if hasattr(self, key)] + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +class NodeEvent(Event): + def __init__(self, anchor, start_mark=None, end_mark=None): + self.anchor = anchor + self.start_mark = start_mark + self.end_mark = end_mark + +class CollectionStartEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, + flow_style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class CollectionEndEvent(Event): + pass + +# Implementations. + +class StreamStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndEvent(Event): + pass + +class DocumentStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None, version=None, tags=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + self.version = version + self.tags = tags + +class DocumentEndEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + +class AliasEvent(NodeEvent): + pass + +class ScalarEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, value, + start_mark=None, end_mark=None, style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class SequenceStartEvent(CollectionStartEvent): + pass + +class SequenceEndEvent(CollectionEndEvent): + pass + +class MappingStartEvent(CollectionStartEvent): + pass + +class MappingEndEvent(CollectionEndEvent): + pass + diff --git a/testing/originalnodep/yaml/loader.py b/testing/originalnodep/yaml/loader.py new file mode 100644 index 00000000..e90c1122 --- /dev/null +++ b/testing/originalnodep/yaml/loader.py @@ -0,0 +1,63 @@ + +__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader'] + +from .reader import * +from .scanner import * +from .parser import * +from .composer import * +from .constructor import * +from .resolver import * + +class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) + +# UnsafeLoader is the same as Loader (which is and was always unsafe on +# untrusted input). Use of either Loader or UnsafeLoader should be rare, since +# FullLoad should be able to load almost all YAML safely. Loader is left intact +# to ensure backwards compatibility. +class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) diff --git a/testing/originalnodep/yaml/nodes.py b/testing/originalnodep/yaml/nodes.py new file mode 100644 index 00000000..c4f070c4 --- /dev/null +++ b/testing/originalnodep/yaml/nodes.py @@ -0,0 +1,49 @@ + +class Node(object): + def __init__(self, tag, value, start_mark, end_mark): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + value = self.value + #if isinstance(value, list): + # if len(value) == 0: + # value = '' + # elif len(value) == 1: + # value = '<1 item>' + # else: + # value = '<%d items>' % len(value) + #else: + # if len(value) > 75: + # value = repr(value[:70]+u' ... ') + # else: + # value = repr(value) + value = repr(value) + return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) + +class ScalarNode(Node): + id = 'scalar' + def __init__(self, tag, value, + start_mark=None, end_mark=None, style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class CollectionNode(Node): + def __init__(self, tag, value, + start_mark=None, end_mark=None, flow_style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class SequenceNode(CollectionNode): + id = 'sequence' + +class MappingNode(CollectionNode): + id = 'mapping' + diff --git a/testing/originalnodep/yaml/parser.py b/testing/originalnodep/yaml/parser.py new file mode 100644 index 00000000..13a5995d --- /dev/null +++ b/testing/originalnodep/yaml/parser.py @@ -0,0 +1,589 @@ + +# The following YAML grammar is LL(1) and is parsed by a recursive descent +# parser. +# +# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +# implicit_document ::= block_node DOCUMENT-END* +# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +# block_node_or_indentless_sequence ::= +# ALIAS +# | properties (block_content | indentless_block_sequence)? +# | block_content +# | indentless_block_sequence +# block_node ::= ALIAS +# | properties block_content? +# | block_content +# flow_node ::= ALIAS +# | properties flow_content? +# | flow_content +# properties ::= TAG ANCHOR? | ANCHOR TAG? +# block_content ::= block_collection | flow_collection | SCALAR +# flow_content ::= flow_collection | SCALAR +# block_collection ::= block_sequence | block_mapping +# flow_collection ::= flow_sequence | flow_mapping +# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +# indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +# block_mapping ::= BLOCK-MAPPING_START +# ((KEY block_node_or_indentless_sequence?)? +# (VALUE block_node_or_indentless_sequence?)?)* +# BLOCK-END +# flow_sequence ::= FLOW-SEQUENCE-START +# (flow_sequence_entry FLOW-ENTRY)* +# flow_sequence_entry? +# FLOW-SEQUENCE-END +# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# flow_mapping ::= FLOW-MAPPING-START +# (flow_mapping_entry FLOW-ENTRY)* +# flow_mapping_entry? +# FLOW-MAPPING-END +# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# +# FIRST sets: +# +# stream: { STREAM-START } +# explicit_document: { DIRECTIVE DOCUMENT-START } +# implicit_document: FIRST(block_node) +# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_sequence: { BLOCK-SEQUENCE-START } +# block_mapping: { BLOCK-MAPPING-START } +# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY } +# indentless_sequence: { ENTRY } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_sequence: { FLOW-SEQUENCE-START } +# flow_mapping: { FLOW-MAPPING-START } +# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } +# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } + +__all__ = ['Parser', 'ParserError'] + +from .error import MarkedYAMLError +from .tokens import * +from .events import * +from .scanner import * + +class ParserError(MarkedYAMLError): + pass + +class Parser: + # Since writing a recursive-descendant parser is a straightforward task, we + # do not give many comments here. + + DEFAULT_TAGS = { + '!': '!', + '!!': 'tag:yaml.org,2002:', + } + + def __init__(self): + self.current_event = None + self.yaml_version = None + self.tag_handles = {} + self.states = [] + self.marks = [] + self.state = self.parse_stream_start + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def check_event(self, *choices): + # Check the type of the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + if self.current_event is not None: + if not choices: + return True + for choice in choices: + if isinstance(self.current_event, choice): + return True + return False + + def peek_event(self): + # Get the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + return self.current_event + + def get_event(self): + # Get the next event and proceed further. + if self.current_event is None: + if self.state: + self.current_event = self.state() + value = self.current_event + self.current_event = None + return value + + # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + # implicit_document ::= block_node DOCUMENT-END* + # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + + def parse_stream_start(self): + + # Parse the stream start. + token = self.get_token() + event = StreamStartEvent(token.start_mark, token.end_mark, + encoding=token.encoding) + + # Prepare the next state. + self.state = self.parse_implicit_document_start + + return event + + def parse_implicit_document_start(self): + + # Parse an implicit document. + if not self.check_token(DirectiveToken, DocumentStartToken, + StreamEndToken): + self.tag_handles = self.DEFAULT_TAGS + token = self.peek_token() + start_mark = end_mark = token.start_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=False) + + # Prepare the next state. + self.states.append(self.parse_document_end) + self.state = self.parse_block_node + + return event + + else: + return self.parse_document_start() + + def parse_document_start(self): + + # Parse any extra document end indicators. + while self.check_token(DocumentEndToken): + self.get_token() + + # Parse an explicit document. + if not self.check_token(StreamEndToken): + token = self.peek_token() + start_mark = token.start_mark + version, tags = self.process_directives() + if not self.check_token(DocumentStartToken): + raise ParserError(None, None, + "expected '', but found %r" + % self.peek_token().id, + self.peek_token().start_mark) + token = self.get_token() + end_mark = token.end_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=True, version=version, tags=tags) + self.states.append(self.parse_document_end) + self.state = self.parse_document_content + else: + # Parse the end of the stream. + token = self.get_token() + event = StreamEndEvent(token.start_mark, token.end_mark) + assert not self.states + assert not self.marks + self.state = None + return event + + def parse_document_end(self): + + # Parse the document end. + token = self.peek_token() + start_mark = end_mark = token.start_mark + explicit = False + if self.check_token(DocumentEndToken): + token = self.get_token() + end_mark = token.end_mark + explicit = True + event = DocumentEndEvent(start_mark, end_mark, + explicit=explicit) + + # Prepare the next state. + self.state = self.parse_document_start + + return event + + def parse_document_content(self): + if self.check_token(DirectiveToken, + DocumentStartToken, DocumentEndToken, StreamEndToken): + event = self.process_empty_scalar(self.peek_token().start_mark) + self.state = self.states.pop() + return event + else: + return self.parse_block_node() + + def process_directives(self): + self.yaml_version = None + self.tag_handles = {} + while self.check_token(DirectiveToken): + token = self.get_token() + if token.name == 'YAML': + if self.yaml_version is not None: + raise ParserError(None, None, + "found duplicate YAML directive", token.start_mark) + major, minor = token.value + if major != 1: + raise ParserError(None, None, + "found incompatible YAML document (version 1.* is required)", + token.start_mark) + self.yaml_version = token.value + elif token.name == 'TAG': + handle, prefix = token.value + if handle in self.tag_handles: + raise ParserError(None, None, + "duplicate tag handle %r" % handle, + token.start_mark) + self.tag_handles[handle] = prefix + if self.tag_handles: + value = self.yaml_version, self.tag_handles.copy() + else: + value = self.yaml_version, None + for key in self.DEFAULT_TAGS: + if key not in self.tag_handles: + self.tag_handles[key] = self.DEFAULT_TAGS[key] + return value + + # block_node_or_indentless_sequence ::= ALIAS + # | properties (block_content | indentless_block_sequence)? + # | block_content + # | indentless_block_sequence + # block_node ::= ALIAS + # | properties block_content? + # | block_content + # flow_node ::= ALIAS + # | properties flow_content? + # | flow_content + # properties ::= TAG ANCHOR? | ANCHOR TAG? + # block_content ::= block_collection | flow_collection | SCALAR + # flow_content ::= flow_collection | SCALAR + # block_collection ::= block_sequence | block_mapping + # flow_collection ::= flow_sequence | flow_mapping + + def parse_block_node(self): + return self.parse_node(block=True) + + def parse_flow_node(self): + return self.parse_node() + + def parse_block_node_or_indentless_sequence(self): + return self.parse_node(block=True, indentless_sequence=True) + + def parse_node(self, block=False, indentless_sequence=False): + if self.check_token(AliasToken): + token = self.get_token() + event = AliasEvent(token.value, token.start_mark, token.end_mark) + self.state = self.states.pop() + else: + anchor = None + tag = None + start_mark = end_mark = tag_mark = None + if self.check_token(AnchorToken): + token = self.get_token() + start_mark = token.start_mark + end_mark = token.end_mark + anchor = token.value + if self.check_token(TagToken): + token = self.get_token() + tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + elif self.check_token(TagToken): + token = self.get_token() + start_mark = tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + if self.check_token(AnchorToken): + token = self.get_token() + end_mark = token.end_mark + anchor = token.value + if tag is not None: + handle, suffix = tag + if handle is not None: + if handle not in self.tag_handles: + raise ParserError("while parsing a node", start_mark, + "found undefined tag handle %r" % handle, + tag_mark) + tag = self.tag_handles[handle]+suffix + else: + tag = suffix + #if tag == '!': + # raise ParserError("while parsing a node", start_mark, + # "found non-specific tag '!'", tag_mark, + # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.") + if start_mark is None: + start_mark = end_mark = self.peek_token().start_mark + event = None + implicit = (tag is None or tag == '!') + if indentless_sequence and self.check_token(BlockEntryToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark) + self.state = self.parse_indentless_sequence_entry + else: + if self.check_token(ScalarToken): + token = self.get_token() + end_mark = token.end_mark + if (token.plain and tag is None) or tag == '!': + implicit = (True, False) + elif tag is None: + implicit = (False, True) + else: + implicit = (False, False) + event = ScalarEvent(anchor, tag, implicit, token.value, + start_mark, end_mark, style=token.style) + self.state = self.states.pop() + elif self.check_token(FlowSequenceStartToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_sequence_first_entry + elif self.check_token(FlowMappingStartToken): + end_mark = self.peek_token().end_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_mapping_first_key + elif block and self.check_token(BlockSequenceStartToken): + end_mark = self.peek_token().start_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_sequence_first_entry + elif block and self.check_token(BlockMappingStartToken): + end_mark = self.peek_token().start_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_mapping_first_key + elif anchor is not None or tag is not None: + # Empty scalars are allowed even if a tag or an anchor is + # specified. + event = ScalarEvent(anchor, tag, (implicit, False), '', + start_mark, end_mark) + self.state = self.states.pop() + else: + if block: + node = 'block' + else: + node = 'flow' + token = self.peek_token() + raise ParserError("while parsing a %s node" % node, start_mark, + "expected the node content, but found %r" % token.id, + token.start_mark) + return event + + # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + + def parse_block_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_sequence_entry() + + def parse_block_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, BlockEndToken): + self.states.append(self.parse_block_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_block_sequence_entry + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block collection", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + # indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + + def parse_indentless_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, + KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_indentless_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_indentless_sequence_entry + return self.process_empty_scalar(token.end_mark) + token = self.peek_token() + event = SequenceEndEvent(token.start_mark, token.start_mark) + self.state = self.states.pop() + return event + + # block_mapping ::= BLOCK-MAPPING_START + # ((KEY block_node_or_indentless_sequence?)? + # (VALUE block_node_or_indentless_sequence?)?)* + # BLOCK-END + + def parse_block_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_mapping_key() + + def parse_block_mapping_key(self): + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_value) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_value + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block mapping", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_block_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_key) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_block_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + # flow_sequence ::= FLOW-SEQUENCE-START + # (flow_sequence_entry FLOW-ENTRY)* + # flow_sequence_entry? + # FLOW-SEQUENCE-END + # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + # + # Note that while production rules for both flow_sequence_entry and + # flow_mapping_entry are equal, their interpretations are different. + # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?` + # generate an inline mapping (set syntax). + + def parse_flow_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_sequence_entry(first=True) + + def parse_flow_sequence_entry(self, first=False): + if not self.check_token(FlowSequenceEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow sequence", self.marks[-1], + "expected ',' or ']', but got %r" % token.id, token.start_mark) + + if self.check_token(KeyToken): + token = self.peek_token() + event = MappingStartEvent(None, None, True, + token.start_mark, token.end_mark, + flow_style=True) + self.state = self.parse_flow_sequence_entry_mapping_key + return event + elif not self.check_token(FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry) + return self.parse_flow_node() + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_sequence_entry_mapping_key(self): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_value + return self.process_empty_scalar(token.end_mark) + + def parse_flow_sequence_entry_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_end) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_end + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_sequence_entry_mapping_end + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_sequence_entry_mapping_end(self): + self.state = self.parse_flow_sequence_entry + token = self.peek_token() + return MappingEndEvent(token.start_mark, token.start_mark) + + # flow_mapping ::= FLOW-MAPPING-START + # (flow_mapping_entry FLOW-ENTRY)* + # flow_mapping_entry? + # FLOW-MAPPING-END + # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + + def parse_flow_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_mapping_key(first=True) + + def parse_flow_mapping_key(self, first=False): + if not self.check_token(FlowMappingEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow mapping", self.marks[-1], + "expected ',' or '}', but got %r" % token.id, token.start_mark) + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_value + return self.process_empty_scalar(token.end_mark) + elif not self.check_token(FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_empty_value) + return self.parse_flow_node() + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_key) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_mapping_empty_value(self): + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(self.peek_token().start_mark) + + def process_empty_scalar(self, mark): + return ScalarEvent(None, None, (True, False), '', mark, mark) + diff --git a/testing/originalnodep/yaml/reader.py b/testing/originalnodep/yaml/reader.py new file mode 100644 index 00000000..774b0219 --- /dev/null +++ b/testing/originalnodep/yaml/reader.py @@ -0,0 +1,185 @@ +# This module contains abstractions for the input stream. You don't have to +# looks further, there are no pretty code. +# +# We define two classes here. +# +# Mark(source, line, column) +# It's just a record and its only use is producing nice error messages. +# Parser does not use it for any other purposes. +# +# Reader(source, data) +# Reader determines the encoding of `data` and converts it to unicode. +# Reader provides the following methods and attributes: +# reader.peek(length=1) - return the next `length` characters +# reader.forward(length=1) - move the current position to `length` characters. +# reader.index - the number of the current character. +# reader.line, stream.column - the line and the column of the current character. + +__all__ = ['Reader', 'ReaderError'] + +from .error import YAMLError, Mark + +import codecs, re + +class ReaderError(YAMLError): + + def __init__(self, name, position, character, encoding, reason): + self.name = name + self.character = character + self.position = position + self.encoding = encoding + self.reason = reason + + def __str__(self): + if isinstance(self.character, bytes): + return "'%s' codec can't decode byte #x%02x: %s\n" \ + " in \"%s\", position %d" \ + % (self.encoding, ord(self.character), self.reason, + self.name, self.position) + else: + return "unacceptable character #x%04x: %s\n" \ + " in \"%s\", position %d" \ + % (self.character, self.reason, + self.name, self.position) + +class Reader(object): + # Reader: + # - determines the data encoding and converts it to a unicode string, + # - checks if characters are in allowed range, + # - adds '\0' to the end. + + # Reader accepts + # - a `bytes` object, + # - a `str` object, + # - a file-like object with its `read` method returning `str`, + # - a file-like object with its `read` method returning `unicode`. + + # Yeah, it's ugly and slow. + + def __init__(self, stream): + self.name = None + self.stream = None + self.stream_pointer = 0 + self.eof = True + self.buffer = '' + self.pointer = 0 + self.raw_buffer = None + self.raw_decode = None + self.encoding = None + self.index = 0 + self.line = 0 + self.column = 0 + if isinstance(stream, str): + self.name = "" + self.check_printable(stream) + self.buffer = stream+'\0' + elif isinstance(stream, bytes): + self.name = "" + self.raw_buffer = stream + self.determine_encoding() + else: + self.stream = stream + self.name = getattr(stream, 'name', "") + self.eof = False + self.raw_buffer = None + self.determine_encoding() + + def peek(self, index=0): + try: + return self.buffer[self.pointer+index] + except IndexError: + self.update(index+1) + return self.buffer[self.pointer+index] + + def prefix(self, length=1): + if self.pointer+length >= len(self.buffer): + self.update(length) + return self.buffer[self.pointer:self.pointer+length] + + def forward(self, length=1): + if self.pointer+length+1 >= len(self.buffer): + self.update(length+1) + while length: + ch = self.buffer[self.pointer] + self.pointer += 1 + self.index += 1 + if ch in '\n\x85\u2028\u2029' \ + or (ch == '\r' and self.buffer[self.pointer] != '\n'): + self.line += 1 + self.column = 0 + elif ch != '\uFEFF': + self.column += 1 + length -= 1 + + def get_mark(self): + if self.stream is None: + return Mark(self.name, self.index, self.line, self.column, + self.buffer, self.pointer) + else: + return Mark(self.name, self.index, self.line, self.column, + None, None) + + def determine_encoding(self): + while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): + self.update_raw() + if isinstance(self.raw_buffer, bytes): + if self.raw_buffer.startswith(codecs.BOM_UTF16_LE): + self.raw_decode = codecs.utf_16_le_decode + self.encoding = 'utf-16-le' + elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE): + self.raw_decode = codecs.utf_16_be_decode + self.encoding = 'utf-16-be' + else: + self.raw_decode = codecs.utf_8_decode + self.encoding = 'utf-8' + self.update(1) + + NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]') + def check_printable(self, data): + match = self.NON_PRINTABLE.search(data) + if match: + character = match.group() + position = self.index+(len(self.buffer)-self.pointer)+match.start() + raise ReaderError(self.name, position, ord(character), + 'unicode', "special characters are not allowed") + + def update(self, length): + if self.raw_buffer is None: + return + self.buffer = self.buffer[self.pointer:] + self.pointer = 0 + while len(self.buffer) < length: + if not self.eof: + self.update_raw() + if self.raw_decode is not None: + try: + data, converted = self.raw_decode(self.raw_buffer, + 'strict', self.eof) + except UnicodeDecodeError as exc: + character = self.raw_buffer[exc.start] + if self.stream is not None: + position = self.stream_pointer-len(self.raw_buffer)+exc.start + else: + position = exc.start + raise ReaderError(self.name, position, character, + exc.encoding, exc.reason) + else: + data = self.raw_buffer + converted = len(data) + self.check_printable(data) + self.buffer += data + self.raw_buffer = self.raw_buffer[converted:] + if self.eof: + self.buffer += '\0' + self.raw_buffer = None + break + + def update_raw(self, size=4096): + data = self.stream.read(size) + if self.raw_buffer is None: + self.raw_buffer = data + else: + self.raw_buffer += data + self.stream_pointer += len(data) + if not data: + self.eof = True diff --git a/testing/originalnodep/yaml/representer.py b/testing/originalnodep/yaml/representer.py new file mode 100644 index 00000000..808ca06d --- /dev/null +++ b/testing/originalnodep/yaml/representer.py @@ -0,0 +1,389 @@ + +__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', + 'RepresenterError'] + +from .error import * +from .nodes import * + +import datetime, copyreg, types, base64, collections + +class RepresenterError(YAMLError): + pass + +class BaseRepresenter: + + yaml_representers = {} + yaml_multi_representers = {} + + def __init__(self, default_style=None, default_flow_style=False, sort_keys=True): + self.default_style = default_style + self.sort_keys = sort_keys + self.default_flow_style = default_flow_style + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent(self, data): + node = self.represent_data(data) + self.serialize(node) + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent_data(self, data): + if self.ignore_aliases(data): + self.alias_key = None + else: + self.alias_key = id(data) + if self.alias_key is not None: + if self.alias_key in self.represented_objects: + node = self.represented_objects[self.alias_key] + #if node is None: + # raise RepresenterError("recursive objects are not allowed: %r" % data) + return node + #self.represented_objects[alias_key] = None + self.object_keeper.append(data) + data_types = type(data).__mro__ + if data_types[0] in self.yaml_representers: + node = self.yaml_representers[data_types[0]](self, data) + else: + for data_type in data_types: + if data_type in self.yaml_multi_representers: + node = self.yaml_multi_representers[data_type](self, data) + break + else: + if None in self.yaml_multi_representers: + node = self.yaml_multi_representers[None](self, data) + elif None in self.yaml_representers: + node = self.yaml_representers[None](self, data) + else: + node = ScalarNode(None, str(data)) + #if alias_key is not None: + # self.represented_objects[alias_key] = node + return node + + @classmethod + def add_representer(cls, data_type, representer): + if not 'yaml_representers' in cls.__dict__: + cls.yaml_representers = cls.yaml_representers.copy() + cls.yaml_representers[data_type] = representer + + @classmethod + def add_multi_representer(cls, data_type, representer): + if not 'yaml_multi_representers' in cls.__dict__: + cls.yaml_multi_representers = cls.yaml_multi_representers.copy() + cls.yaml_multi_representers[data_type] = representer + + def represent_scalar(self, tag, value, style=None): + if style is None: + style = self.default_style + node = ScalarNode(tag, value, style=style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + return node + + def represent_sequence(self, tag, sequence, flow_style=None): + value = [] + node = SequenceNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + for item in sequence: + node_item = self.represent_data(item) + if not (isinstance(node_item, ScalarNode) and not node_item.style): + best_style = False + value.append(node_item) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def represent_mapping(self, tag, mapping, flow_style=None): + value = [] + node = MappingNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + if hasattr(mapping, 'items'): + mapping = list(mapping.items()) + if self.sort_keys: + try: + mapping = sorted(mapping) + except TypeError: + pass + for item_key, item_value in mapping: + node_key = self.represent_data(item_key) + node_value = self.represent_data(item_value) + if not (isinstance(node_key, ScalarNode) and not node_key.style): + best_style = False + if not (isinstance(node_value, ScalarNode) and not node_value.style): + best_style = False + value.append((node_key, node_value)) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def ignore_aliases(self, data): + return False + +class SafeRepresenter(BaseRepresenter): + + def ignore_aliases(self, data): + if data is None: + return True + if isinstance(data, tuple) and data == (): + return True + if isinstance(data, (str, bytes, bool, int, float)): + return True + + def represent_none(self, data): + return self.represent_scalar('tag:yaml.org,2002:null', 'null') + + def represent_str(self, data): + return self.represent_scalar('tag:yaml.org,2002:str', data) + + def represent_binary(self, data): + if hasattr(base64, 'encodebytes'): + data = base64.encodebytes(data).decode('ascii') + else: + data = base64.encodestring(data).decode('ascii') + return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') + + def represent_bool(self, data): + if data: + value = 'true' + else: + value = 'false' + return self.represent_scalar('tag:yaml.org,2002:bool', value) + + def represent_int(self, data): + return self.represent_scalar('tag:yaml.org,2002:int', str(data)) + + inf_value = 1e300 + while repr(inf_value) != repr(inf_value*inf_value): + inf_value *= inf_value + + def represent_float(self, data): + if data != data or (data == 0.0 and data == 1.0): + value = '.nan' + elif data == self.inf_value: + value = '.inf' + elif data == -self.inf_value: + value = '-.inf' + else: + value = repr(data).lower() + # Note that in some cases `repr(data)` represents a float number + # without the decimal parts. For instance: + # >>> repr(1e17) + # '1e17' + # Unfortunately, this is not a valid float representation according + # to the definition of the `!!float` tag. We fix this by adding + # '.0' before the 'e' symbol. + if '.' not in value and 'e' in value: + value = value.replace('e', '.0e', 1) + return self.represent_scalar('tag:yaml.org,2002:float', value) + + def represent_list(self, data): + #pairs = (len(data) > 0 and isinstance(data, list)) + #if pairs: + # for item in data: + # if not isinstance(item, tuple) or len(item) != 2: + # pairs = False + # break + #if not pairs: + return self.represent_sequence('tag:yaml.org,2002:seq', data) + #value = [] + #for item_key, item_value in data: + # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', + # [(item_key, item_value)])) + #return SequenceNode(u'tag:yaml.org,2002:pairs', value) + + def represent_dict(self, data): + return self.represent_mapping('tag:yaml.org,2002:map', data) + + def represent_set(self, data): + value = {} + for key in data: + value[key] = None + return self.represent_mapping('tag:yaml.org,2002:set', value) + + def represent_date(self, data): + value = data.isoformat() + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_datetime(self, data): + value = data.isoformat(' ') + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) + + def represent_undefined(self, data): + raise RepresenterError("cannot represent an object", data) + +SafeRepresenter.add_representer(type(None), + SafeRepresenter.represent_none) + +SafeRepresenter.add_representer(str, + SafeRepresenter.represent_str) + +SafeRepresenter.add_representer(bytes, + SafeRepresenter.represent_binary) + +SafeRepresenter.add_representer(bool, + SafeRepresenter.represent_bool) + +SafeRepresenter.add_representer(int, + SafeRepresenter.represent_int) + +SafeRepresenter.add_representer(float, + SafeRepresenter.represent_float) + +SafeRepresenter.add_representer(list, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(tuple, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(dict, + SafeRepresenter.represent_dict) + +SafeRepresenter.add_representer(set, + SafeRepresenter.represent_set) + +SafeRepresenter.add_representer(datetime.date, + SafeRepresenter.represent_date) + +SafeRepresenter.add_representer(datetime.datetime, + SafeRepresenter.represent_datetime) + +SafeRepresenter.add_representer(None, + SafeRepresenter.represent_undefined) + +class Representer(SafeRepresenter): + + def represent_complex(self, data): + if data.imag == 0.0: + data = '%r' % data.real + elif data.real == 0.0: + data = '%rj' % data.imag + elif data.imag > 0: + data = '%r+%rj' % (data.real, data.imag) + else: + data = '%r%rj' % (data.real, data.imag) + return self.represent_scalar('tag:yaml.org,2002:python/complex', data) + + def represent_tuple(self, data): + return self.represent_sequence('tag:yaml.org,2002:python/tuple', data) + + def represent_name(self, data): + name = '%s.%s' % (data.__module__, data.__name__) + return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '') + + def represent_module(self, data): + return self.represent_scalar( + 'tag:yaml.org,2002:python/module:'+data.__name__, '') + + def represent_object(self, data): + # We use __reduce__ API to save the data. data.__reduce__ returns + # a tuple of length 2-5: + # (function, args, state, listitems, dictitems) + + # For reconstructing, we calls function(*args), then set its state, + # listitems, and dictitems if they are not None. + + # A special case is when function.__name__ == '__newobj__'. In this + # case we create the object with args[0].__new__(*args). + + # Another special case is when __reduce__ returns a string - we don't + # support it. + + # We produce a !!python/object, !!python/object/new or + # !!python/object/apply node. + + cls = type(data) + if cls in copyreg.dispatch_table: + reduce = copyreg.dispatch_table[cls](data) + elif hasattr(data, '__reduce_ex__'): + reduce = data.__reduce_ex__(2) + elif hasattr(data, '__reduce__'): + reduce = data.__reduce__() + else: + raise RepresenterError("cannot represent an object", data) + reduce = (list(reduce)+[None]*5)[:5] + function, args, state, listitems, dictitems = reduce + args = list(args) + if state is None: + state = {} + if listitems is not None: + listitems = list(listitems) + if dictitems is not None: + dictitems = dict(dictitems) + if function.__name__ == '__newobj__': + function = args[0] + args = args[1:] + tag = 'tag:yaml.org,2002:python/object/new:' + newobj = True + else: + tag = 'tag:yaml.org,2002:python/object/apply:' + newobj = False + function_name = '%s.%s' % (function.__module__, function.__name__) + if not args and not listitems and not dictitems \ + and isinstance(state, dict) and newobj: + return self.represent_mapping( + 'tag:yaml.org,2002:python/object:'+function_name, state) + if not listitems and not dictitems \ + and isinstance(state, dict) and not state: + return self.represent_sequence(tag+function_name, args) + value = {} + if args: + value['args'] = args + if state or not isinstance(state, dict): + value['state'] = state + if listitems: + value['listitems'] = listitems + if dictitems: + value['dictitems'] = dictitems + return self.represent_mapping(tag+function_name, value) + + def represent_ordered_dict(self, data): + # Provide uniform representation across different Python versions. + data_type = type(data) + tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \ + % (data_type.__module__, data_type.__name__) + items = [[key, value] for key, value in data.items()] + return self.represent_sequence(tag, [items]) + +Representer.add_representer(complex, + Representer.represent_complex) + +Representer.add_representer(tuple, + Representer.represent_tuple) + +Representer.add_multi_representer(type, + Representer.represent_name) + +Representer.add_representer(collections.OrderedDict, + Representer.represent_ordered_dict) + +Representer.add_representer(types.FunctionType, + Representer.represent_name) + +Representer.add_representer(types.BuiltinFunctionType, + Representer.represent_name) + +Representer.add_representer(types.ModuleType, + Representer.represent_module) + +Representer.add_multi_representer(object, + Representer.represent_object) + diff --git a/testing/originalnodep/yaml/resolver.py b/testing/originalnodep/yaml/resolver.py new file mode 100644 index 00000000..3522bdaa --- /dev/null +++ b/testing/originalnodep/yaml/resolver.py @@ -0,0 +1,227 @@ + +__all__ = ['BaseResolver', 'Resolver'] + +from .error import * +from .nodes import * + +import re + +class ResolverError(YAMLError): + pass + +class BaseResolver: + + DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str' + DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq' + DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' + + yaml_implicit_resolvers = {} + yaml_path_resolvers = {} + + def __init__(self): + self.resolver_exact_paths = [] + self.resolver_prefix_paths = [] + + @classmethod + def add_implicit_resolver(cls, tag, regexp, first): + if not 'yaml_implicit_resolvers' in cls.__dict__: + implicit_resolvers = {} + for key in cls.yaml_implicit_resolvers: + implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:] + cls.yaml_implicit_resolvers = implicit_resolvers + if first is None: + first = [None] + for ch in first: + cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) + + @classmethod + def add_path_resolver(cls, tag, path, kind=None): + # Note: `add_path_resolver` is experimental. The API could be changed. + # `new_path` is a pattern that is matched against the path from the + # root to the node that is being considered. `node_path` elements are + # tuples `(node_check, index_check)`. `node_check` is a node class: + # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None` + # matches any kind of a node. `index_check` could be `None`, a boolean + # value, a string value, or a number. `None` and `False` match against + # any _value_ of sequence and mapping nodes. `True` matches against + # any _key_ of a mapping node. A string `index_check` matches against + # a mapping value that corresponds to a scalar key which content is + # equal to the `index_check` value. An integer `index_check` matches + # against a sequence value with the index equal to `index_check`. + if not 'yaml_path_resolvers' in cls.__dict__: + cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy() + new_path = [] + for element in path: + if isinstance(element, (list, tuple)): + if len(element) == 2: + node_check, index_check = element + elif len(element) == 1: + node_check = element[0] + index_check = True + else: + raise ResolverError("Invalid path element: %s" % element) + else: + node_check = None + index_check = element + if node_check is str: + node_check = ScalarNode + elif node_check is list: + node_check = SequenceNode + elif node_check is dict: + node_check = MappingNode + elif node_check not in [ScalarNode, SequenceNode, MappingNode] \ + and not isinstance(node_check, str) \ + and node_check is not None: + raise ResolverError("Invalid node checker: %s" % node_check) + if not isinstance(index_check, (str, int)) \ + and index_check is not None: + raise ResolverError("Invalid index checker: %s" % index_check) + new_path.append((node_check, index_check)) + if kind is str: + kind = ScalarNode + elif kind is list: + kind = SequenceNode + elif kind is dict: + kind = MappingNode + elif kind not in [ScalarNode, SequenceNode, MappingNode] \ + and kind is not None: + raise ResolverError("Invalid node kind: %s" % kind) + cls.yaml_path_resolvers[tuple(new_path), kind] = tag + + def descend_resolver(self, current_node, current_index): + if not self.yaml_path_resolvers: + return + exact_paths = {} + prefix_paths = [] + if current_node: + depth = len(self.resolver_prefix_paths) + for path, kind in self.resolver_prefix_paths[-1]: + if self.check_resolver_prefix(depth, path, kind, + current_node, current_index): + if len(path) > depth: + prefix_paths.append((path, kind)) + else: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + for path, kind in self.yaml_path_resolvers: + if not path: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + prefix_paths.append((path, kind)) + self.resolver_exact_paths.append(exact_paths) + self.resolver_prefix_paths.append(prefix_paths) + + def ascend_resolver(self): + if not self.yaml_path_resolvers: + return + self.resolver_exact_paths.pop() + self.resolver_prefix_paths.pop() + + def check_resolver_prefix(self, depth, path, kind, + current_node, current_index): + node_check, index_check = path[depth-1] + if isinstance(node_check, str): + if current_node.tag != node_check: + return + elif node_check is not None: + if not isinstance(current_node, node_check): + return + if index_check is True and current_index is not None: + return + if (index_check is False or index_check is None) \ + and current_index is None: + return + if isinstance(index_check, str): + if not (isinstance(current_index, ScalarNode) + and index_check == current_index.value): + return + elif isinstance(index_check, int) and not isinstance(index_check, bool): + if index_check != current_index: + return + return True + + def resolve(self, kind, value, implicit): + if kind is ScalarNode and implicit[0]: + if value == '': + resolvers = self.yaml_implicit_resolvers.get('', []) + else: + resolvers = self.yaml_implicit_resolvers.get(value[0], []) + wildcard_resolvers = self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers + wildcard_resolvers: + if regexp.match(value): + return tag + implicit = implicit[1] + if self.yaml_path_resolvers: + exact_paths = self.resolver_exact_paths[-1] + if kind in exact_paths: + return exact_paths[kind] + if None in exact_paths: + return exact_paths[None] + if kind is ScalarNode: + return self.DEFAULT_SCALAR_TAG + elif kind is SequenceNode: + return self.DEFAULT_SEQUENCE_TAG + elif kind is MappingNode: + return self.DEFAULT_MAPPING_TAG + +class Resolver(BaseResolver): + pass + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:bool', + re.compile(r'''^(?:yes|Yes|YES|no|No|NO + |true|True|TRUE|false|False|FALSE + |on|On|ON|off|Off|OFF)$''', re.X), + list('yYnNtTfFoO')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:float', + re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + |\.[0-9][0-9_]*(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN))$''', re.X), + list('-+0123456789.')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:int', + re.compile(r'''^(?:[-+]?0b[0-1_]+ + |[-+]?0[0-7_]+ + |[-+]?(?:0|[1-9][0-9_]*) + |[-+]?0x[0-9a-fA-F_]+ + |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), + list('-+0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:merge', + re.compile(r'^(?:<<)$'), + ['<']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:null', + re.compile(r'''^(?: ~ + |null|Null|NULL + | )$''', re.X), + ['~', 'n', 'N', '']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:timestamp', + re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? + (?:[Tt]|[ \t]+)[0-9][0-9]? + :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? + (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), + list('0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:value', + re.compile(r'^(?:=)$'), + ['=']) + +# The following resolver is only for documentation purposes. It cannot work +# because plain scalars cannot start with '!', '&', or '*'. +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:yaml', + re.compile(r'^(?:!|&|\*)$'), + list('!&*')) + diff --git a/testing/originalnodep/yaml/scanner.py b/testing/originalnodep/yaml/scanner.py new file mode 100644 index 00000000..de925b07 --- /dev/null +++ b/testing/originalnodep/yaml/scanner.py @@ -0,0 +1,1435 @@ + +# Scanner produces tokens of the following types: +# STREAM-START +# STREAM-END +# DIRECTIVE(name, value) +# DOCUMENT-START +# DOCUMENT-END +# BLOCK-SEQUENCE-START +# BLOCK-MAPPING-START +# BLOCK-END +# FLOW-SEQUENCE-START +# FLOW-MAPPING-START +# FLOW-SEQUENCE-END +# FLOW-MAPPING-END +# BLOCK-ENTRY +# FLOW-ENTRY +# KEY +# VALUE +# ALIAS(value) +# ANCHOR(value) +# TAG(value) +# SCALAR(value, plain, style) +# +# Read comments in the Scanner code for more details. +# + +__all__ = ['Scanner', 'ScannerError'] + +from .error import MarkedYAMLError +from .tokens import * + +class ScannerError(MarkedYAMLError): + pass + +class SimpleKey: + # See below simple keys treatment. + + def __init__(self, token_number, required, index, line, column, mark): + self.token_number = token_number + self.required = required + self.index = index + self.line = line + self.column = column + self.mark = mark + +class Scanner: + + def __init__(self): + """Initialize the scanner.""" + # It is assumed that Scanner and Reader will have a common descendant. + # Reader do the dirty work of checking for BOM and converting the + # input data to Unicode. It also adds NUL to the end. + # + # Reader supports the following methods + # self.peek(i=0) # peek the next i-th character + # self.prefix(l=1) # peek the next l characters + # self.forward(l=1) # read the next l characters and move the pointer. + + # Had we reached the end of the stream? + self.done = False + + # The number of unclosed '{' and '['. `flow_level == 0` means block + # context. + self.flow_level = 0 + + # List of processed tokens that are not yet emitted. + self.tokens = [] + + # Add the STREAM-START token. + self.fetch_stream_start() + + # Number of tokens that were emitted through the `get_token` method. + self.tokens_taken = 0 + + # The current indentation level. + self.indent = -1 + + # Past indentation levels. + self.indents = [] + + # Variables related to simple keys treatment. + + # A simple key is a key that is not denoted by the '?' indicator. + # Example of simple keys: + # --- + # block simple key: value + # ? not a simple key: + # : { flow simple key: value } + # We emit the KEY token before all keys, so when we find a potential + # simple key, we try to locate the corresponding ':' indicator. + # Simple keys should be limited to a single line and 1024 characters. + + # Can a simple key start at the current position? A simple key may + # start: + # - at the beginning of the line, not counting indentation spaces + # (in block context), + # - after '{', '[', ',' (in the flow context), + # - after '?', ':', '-' (in the block context). + # In the block context, this flag also signifies if a block collection + # may start at the current position. + self.allow_simple_key = True + + # Keep track of possible simple keys. This is a dictionary. The key + # is `flow_level`; there can be no more that one possible simple key + # for each level. The value is a SimpleKey record: + # (token_number, required, index, line, column, mark) + # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow), + # '[', or '{' tokens. + self.possible_simple_keys = {} + + # Public methods. + + def check_token(self, *choices): + # Check if the next token is one of the given types. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + if not choices: + return True + for choice in choices: + if isinstance(self.tokens[0], choice): + return True + return False + + def peek_token(self): + # Return the next token, but do not delete if from the queue. + # Return None if no more tokens. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + return self.tokens[0] + else: + return None + + def get_token(self): + # Return the next token. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + self.tokens_taken += 1 + return self.tokens.pop(0) + + # Private methods. + + def need_more_tokens(self): + if self.done: + return False + if not self.tokens: + return True + # The current token may be a potential simple key, so we + # need to look further. + self.stale_possible_simple_keys() + if self.next_possible_simple_key() == self.tokens_taken: + return True + + def fetch_more_tokens(self): + + # Eat whitespaces and comments until we reach the next token. + self.scan_to_next_token() + + # Remove obsolete possible simple keys. + self.stale_possible_simple_keys() + + # Compare the current indentation and column. It may add some tokens + # and decrease the current indentation level. + self.unwind_indent(self.column) + + # Peek the next character. + ch = self.peek() + + # Is it the end of stream? + if ch == '\0': + return self.fetch_stream_end() + + # Is it a directive? + if ch == '%' and self.check_directive(): + return self.fetch_directive() + + # Is it the document start? + if ch == '-' and self.check_document_start(): + return self.fetch_document_start() + + # Is it the document end? + if ch == '.' and self.check_document_end(): + return self.fetch_document_end() + + # TODO: support for BOM within a stream. + #if ch == '\uFEFF': + # return self.fetch_bom() <-- issue BOMToken + + # Note: the order of the following checks is NOT significant. + + # Is it the flow sequence start indicator? + if ch == '[': + return self.fetch_flow_sequence_start() + + # Is it the flow mapping start indicator? + if ch == '{': + return self.fetch_flow_mapping_start() + + # Is it the flow sequence end indicator? + if ch == ']': + return self.fetch_flow_sequence_end() + + # Is it the flow mapping end indicator? + if ch == '}': + return self.fetch_flow_mapping_end() + + # Is it the flow entry indicator? + if ch == ',': + return self.fetch_flow_entry() + + # Is it the block entry indicator? + if ch == '-' and self.check_block_entry(): + return self.fetch_block_entry() + + # Is it the key indicator? + if ch == '?' and self.check_key(): + return self.fetch_key() + + # Is it the value indicator? + if ch == ':' and self.check_value(): + return self.fetch_value() + + # Is it an alias? + if ch == '*': + return self.fetch_alias() + + # Is it an anchor? + if ch == '&': + return self.fetch_anchor() + + # Is it a tag? + if ch == '!': + return self.fetch_tag() + + # Is it a literal scalar? + if ch == '|' and not self.flow_level: + return self.fetch_literal() + + # Is it a folded scalar? + if ch == '>' and not self.flow_level: + return self.fetch_folded() + + # Is it a single quoted scalar? + if ch == '\'': + return self.fetch_single() + + # Is it a double quoted scalar? + if ch == '\"': + return self.fetch_double() + + # It must be a plain scalar then. + if self.check_plain(): + return self.fetch_plain() + + # No? It's an error. Let's produce a nice error message. + raise ScannerError("while scanning for the next token", None, + "found character %r that cannot start any token" % ch, + self.get_mark()) + + # Simple keys treatment. + + def next_possible_simple_key(self): + # Return the number of the nearest possible simple key. Actually we + # don't need to loop through the whole dictionary. We may replace it + # with the following code: + # if not self.possible_simple_keys: + # return None + # return self.possible_simple_keys[ + # min(self.possible_simple_keys.keys())].token_number + min_token_number = None + for level in self.possible_simple_keys: + key = self.possible_simple_keys[level] + if min_token_number is None or key.token_number < min_token_number: + min_token_number = key.token_number + return min_token_number + + def stale_possible_simple_keys(self): + # Remove entries that are no longer possible simple keys. According to + # the YAML specification, simple keys + # - should be limited to a single line, + # - should be no longer than 1024 characters. + # Disabling this procedure will allow simple keys of any length and + # height (may cause problems if indentation is broken though). + for level in list(self.possible_simple_keys): + key = self.possible_simple_keys[level] + if key.line != self.line \ + or self.index-key.index > 1024: + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + del self.possible_simple_keys[level] + + def save_possible_simple_key(self): + # The next token may start a simple key. We check if it's possible + # and save its position. This function is called for + # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'. + + # Check if a simple key is required at the current position. + required = not self.flow_level and self.indent == self.column + + # The next token might be a simple key. Let's save it's number and + # position. + if self.allow_simple_key: + self.remove_possible_simple_key() + token_number = self.tokens_taken+len(self.tokens) + key = SimpleKey(token_number, required, + self.index, self.line, self.column, self.get_mark()) + self.possible_simple_keys[self.flow_level] = key + + def remove_possible_simple_key(self): + # Remove the saved possible key position at the current flow level. + if self.flow_level in self.possible_simple_keys: + key = self.possible_simple_keys[self.flow_level] + + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + + del self.possible_simple_keys[self.flow_level] + + # Indentation functions. + + def unwind_indent(self, column): + + ## In flow context, tokens should respect indentation. + ## Actually the condition should be `self.indent >= column` according to + ## the spec. But this condition will prohibit intuitively correct + ## constructions such as + ## key : { + ## } + #if self.flow_level and self.indent > column: + # raise ScannerError(None, None, + # "invalid indentation or unclosed '[' or '{'", + # self.get_mark()) + + # In the flow context, indentation is ignored. We make the scanner less + # restrictive then specification requires. + if self.flow_level: + return + + # In block context, we may need to issue the BLOCK-END tokens. + while self.indent > column: + mark = self.get_mark() + self.indent = self.indents.pop() + self.tokens.append(BlockEndToken(mark, mark)) + + def add_indent(self, column): + # Check if we need to increase indentation. + if self.indent < column: + self.indents.append(self.indent) + self.indent = column + return True + return False + + # Fetchers. + + def fetch_stream_start(self): + # We always add STREAM-START as the first token and STREAM-END as the + # last token. + + # Read the token. + mark = self.get_mark() + + # Add STREAM-START. + self.tokens.append(StreamStartToken(mark, mark, + encoding=self.encoding)) + + + def fetch_stream_end(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + self.possible_simple_keys = {} + + # Read the token. + mark = self.get_mark() + + # Add STREAM-END. + self.tokens.append(StreamEndToken(mark, mark)) + + # The steam is finished. + self.done = True + + def fetch_directive(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Scan and add DIRECTIVE. + self.tokens.append(self.scan_directive()) + + def fetch_document_start(self): + self.fetch_document_indicator(DocumentStartToken) + + def fetch_document_end(self): + self.fetch_document_indicator(DocumentEndToken) + + def fetch_document_indicator(self, TokenClass): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. Note that there could not be a block collection + # after '---'. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Add DOCUMENT-START or DOCUMENT-END. + start_mark = self.get_mark() + self.forward(3) + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_start(self): + self.fetch_flow_collection_start(FlowSequenceStartToken) + + def fetch_flow_mapping_start(self): + self.fetch_flow_collection_start(FlowMappingStartToken) + + def fetch_flow_collection_start(self, TokenClass): + + # '[' and '{' may start a simple key. + self.save_possible_simple_key() + + # Increase the flow level. + self.flow_level += 1 + + # Simple keys are allowed after '[' and '{'. + self.allow_simple_key = True + + # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_end(self): + self.fetch_flow_collection_end(FlowSequenceEndToken) + + def fetch_flow_mapping_end(self): + self.fetch_flow_collection_end(FlowMappingEndToken) + + def fetch_flow_collection_end(self, TokenClass): + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Decrease the flow level. + self.flow_level -= 1 + + # No simple keys after ']' or '}'. + self.allow_simple_key = False + + # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_entry(self): + + # Simple keys are allowed after ','. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add FLOW-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(FlowEntryToken(start_mark, end_mark)) + + def fetch_block_entry(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a new entry? + if not self.allow_simple_key: + raise ScannerError(None, None, + "sequence entries are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-SEQUENCE-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockSequenceStartToken(mark, mark)) + + # It's an error for the block entry to occur in the flow context, + # but we let the parser detect this. + else: + pass + + # Simple keys are allowed after '-'. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add BLOCK-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(BlockEntryToken(start_mark, end_mark)) + + def fetch_key(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a key (not necessary a simple)? + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping keys are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-MAPPING-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after '?' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add KEY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(KeyToken(start_mark, end_mark)) + + def fetch_value(self): + + # Do we determine a simple key? + if self.flow_level in self.possible_simple_keys: + + # Add KEY. + key = self.possible_simple_keys[self.flow_level] + del self.possible_simple_keys[self.flow_level] + self.tokens.insert(key.token_number-self.tokens_taken, + KeyToken(key.mark, key.mark)) + + # If this key starts a new block mapping, we need to add + # BLOCK-MAPPING-START. + if not self.flow_level: + if self.add_indent(key.column): + self.tokens.insert(key.token_number-self.tokens_taken, + BlockMappingStartToken(key.mark, key.mark)) + + # There cannot be two simple keys one after another. + self.allow_simple_key = False + + # It must be a part of a complex key. + else: + + # Block context needs additional checks. + # (Do we really need them? They will be caught by the parser + # anyway.) + if not self.flow_level: + + # We are allowed to start a complex value if and only if + # we can start a simple key. + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping values are not allowed here", + self.get_mark()) + + # If this value starts a new block mapping, we need to add + # BLOCK-MAPPING-START. It will be detected as an error later by + # the parser. + if not self.flow_level: + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after ':' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add VALUE. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(ValueToken(start_mark, end_mark)) + + def fetch_alias(self): + + # ALIAS could be a simple key. + self.save_possible_simple_key() + + # No simple keys after ALIAS. + self.allow_simple_key = False + + # Scan and add ALIAS. + self.tokens.append(self.scan_anchor(AliasToken)) + + def fetch_anchor(self): + + # ANCHOR could start a simple key. + self.save_possible_simple_key() + + # No simple keys after ANCHOR. + self.allow_simple_key = False + + # Scan and add ANCHOR. + self.tokens.append(self.scan_anchor(AnchorToken)) + + def fetch_tag(self): + + # TAG could start a simple key. + self.save_possible_simple_key() + + # No simple keys after TAG. + self.allow_simple_key = False + + # Scan and add TAG. + self.tokens.append(self.scan_tag()) + + def fetch_literal(self): + self.fetch_block_scalar(style='|') + + def fetch_folded(self): + self.fetch_block_scalar(style='>') + + def fetch_block_scalar(self, style): + + # A simple key may follow a block scalar. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Scan and add SCALAR. + self.tokens.append(self.scan_block_scalar(style)) + + def fetch_single(self): + self.fetch_flow_scalar(style='\'') + + def fetch_double(self): + self.fetch_flow_scalar(style='"') + + def fetch_flow_scalar(self, style): + + # A flow scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after flow scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. + self.tokens.append(self.scan_flow_scalar(style)) + + def fetch_plain(self): + + # A plain scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after plain scalars. But note that `scan_plain` will + # change this flag if the scan is finished at the beginning of the + # line. + self.allow_simple_key = False + + # Scan and add SCALAR. May change `allow_simple_key`. + self.tokens.append(self.scan_plain()) + + # Checkers. + + def check_directive(self): + + # DIRECTIVE: ^ '%' ... + # The '%' indicator is already checked. + if self.column == 0: + return True + + def check_document_start(self): + + # DOCUMENT-START: ^ '---' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '---' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_document_end(self): + + # DOCUMENT-END: ^ '...' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '...' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_block_entry(self): + + # BLOCK-ENTRY: '-' (' '|'\n') + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_key(self): + + # KEY(flow context): '?' + if self.flow_level: + return True + + # KEY(block context): '?' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_value(self): + + # VALUE(flow context): ':' + if self.flow_level: + return True + + # VALUE(block context): ':' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_plain(self): + + # A plain scalar may start with any non-space character except: + # '-', '?', ':', ',', '[', ']', '{', '}', + # '#', '&', '*', '!', '|', '>', '\'', '\"', + # '%', '@', '`'. + # + # It may also start with + # '-', '?', ':' + # if it is followed by a non-space character. + # + # Note that we limit the last rule to the block context (except the + # '-' character) because we want the flow context to be space + # independent. + ch = self.peek() + return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ + or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' + and (ch == '-' or (not self.flow_level and ch in '?:'))) + + # Scanners. + + def scan_to_next_token(self): + # We ignore spaces, line breaks and comments. + # If we find a line break in the block context, we set the flag + # `allow_simple_key` on. + # The byte order mark is stripped if it's the first character in the + # stream. We do not yet support BOM inside the stream as the + # specification requires. Any such mark will be considered as a part + # of the document. + # + # TODO: We need to make tab handling rules more sane. A good rule is + # Tabs cannot precede tokens + # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END, + # KEY(block), VALUE(block), BLOCK-ENTRY + # So the checking code is + # if : + # self.allow_simple_keys = False + # We also need to add the check for `allow_simple_keys == True` to + # `unwind_indent` before issuing BLOCK-END. + # Scanners for block, flow, and plain scalars need to be modified. + + if self.index == 0 and self.peek() == '\uFEFF': + self.forward() + found = False + while not found: + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + if self.scan_line_break(): + if not self.flow_level: + self.allow_simple_key = True + else: + found = True + + def scan_directive(self): + # See the specification for details. + start_mark = self.get_mark() + self.forward() + name = self.scan_directive_name(start_mark) + value = None + if name == 'YAML': + value = self.scan_yaml_directive_value(start_mark) + end_mark = self.get_mark() + elif name == 'TAG': + value = self.scan_tag_directive_value(start_mark) + end_mark = self.get_mark() + else: + end_mark = self.get_mark() + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + self.scan_directive_ignored_line(start_mark) + return DirectiveToken(name, value, start_mark, end_mark) + + def scan_directive_name(self, start_mark): + # See the specification for details. + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + return value + + def scan_yaml_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + major = self.scan_yaml_directive_number(start_mark) + if self.peek() != '.': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or '.', but found %r" % self.peek(), + self.get_mark()) + self.forward() + minor = self.scan_yaml_directive_number(start_mark) + if self.peek() not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or ' ', but found %r" % self.peek(), + self.get_mark()) + return (major, minor) + + def scan_yaml_directive_number(self, start_mark): + # See the specification for details. + ch = self.peek() + if not ('0' <= ch <= '9'): + raise ScannerError("while scanning a directive", start_mark, + "expected a digit, but found %r" % ch, self.get_mark()) + length = 0 + while '0' <= self.peek(length) <= '9': + length += 1 + value = int(self.prefix(length)) + self.forward(length) + return value + + def scan_tag_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + handle = self.scan_tag_directive_handle(start_mark) + while self.peek() == ' ': + self.forward() + prefix = self.scan_tag_directive_prefix(start_mark) + return (handle, prefix) + + def scan_tag_directive_handle(self, start_mark): + # See the specification for details. + value = self.scan_tag_handle('directive', start_mark) + ch = self.peek() + if ch != ' ': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_tag_directive_prefix(self, start_mark): + # See the specification for details. + value = self.scan_tag_uri('directive', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_directive_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a comment or a line break, but found %r" + % ch, self.get_mark()) + self.scan_line_break() + + def scan_anchor(self, TokenClass): + # The specification does not restrict characters for anchors and + # aliases. This may lead to problems, for instance, the document: + # [ *alias, value ] + # can be interpreted in two ways, as + # [ "value" ] + # and + # [ *alias , "value" ] + # Therefore we restrict aliases to numbers and ASCII letters. + start_mark = self.get_mark() + indicator = self.peek() + if indicator == '*': + name = 'alias' + else: + name = 'anchor' + self.forward() + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`': + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + end_mark = self.get_mark() + return TokenClass(value, start_mark, end_mark) + + def scan_tag(self): + # See the specification for details. + start_mark = self.get_mark() + ch = self.peek(1) + if ch == '<': + handle = None + self.forward(2) + suffix = self.scan_tag_uri('tag', start_mark) + if self.peek() != '>': + raise ScannerError("while parsing a tag", start_mark, + "expected '>', but found %r" % self.peek(), + self.get_mark()) + self.forward() + elif ch in '\0 \t\r\n\x85\u2028\u2029': + handle = None + suffix = '!' + self.forward() + else: + length = 1 + use_handle = False + while ch not in '\0 \r\n\x85\u2028\u2029': + if ch == '!': + use_handle = True + break + length += 1 + ch = self.peek(length) + handle = '!' + if use_handle: + handle = self.scan_tag_handle('tag', start_mark) + else: + handle = '!' + self.forward() + suffix = self.scan_tag_uri('tag', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a tag", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + value = (handle, suffix) + end_mark = self.get_mark() + return TagToken(value, start_mark, end_mark) + + def scan_block_scalar(self, style): + # See the specification for details. + + if style == '>': + folded = True + else: + folded = False + + chunks = [] + start_mark = self.get_mark() + + # Scan the header. + self.forward() + chomping, increment = self.scan_block_scalar_indicators(start_mark) + self.scan_block_scalar_ignored_line(start_mark) + + # Determine the indentation level and go to the first non-empty line. + min_indent = self.indent+1 + if min_indent < 1: + min_indent = 1 + if increment is None: + breaks, max_indent, end_mark = self.scan_block_scalar_indentation() + indent = max(min_indent, max_indent) + else: + indent = min_indent+increment-1 + breaks, end_mark = self.scan_block_scalar_breaks(indent) + line_break = '' + + # Scan the inner part of the block scalar. + while self.column == indent and self.peek() != '\0': + chunks.extend(breaks) + leading_non_space = self.peek() not in ' \t' + length = 0 + while self.peek(length) not in '\0\r\n\x85\u2028\u2029': + length += 1 + chunks.append(self.prefix(length)) + self.forward(length) + line_break = self.scan_line_break() + breaks, end_mark = self.scan_block_scalar_breaks(indent) + if self.column == indent and self.peek() != '\0': + + # Unfortunately, folding rules are ambiguous. + # + # This is the folding according to the specification: + + if folded and line_break == '\n' \ + and leading_non_space and self.peek() not in ' \t': + if not breaks: + chunks.append(' ') + else: + chunks.append(line_break) + + # This is Clark Evans's interpretation (also in the spec + # examples): + # + #if folded and line_break == '\n': + # if not breaks: + # if self.peek() not in ' \t': + # chunks.append(' ') + # else: + # chunks.append(line_break) + #else: + # chunks.append(line_break) + else: + break + + # Chomp the tail. + if chomping is not False: + chunks.append(line_break) + if chomping is True: + chunks.extend(breaks) + + # We are done. + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + def scan_block_scalar_indicators(self, start_mark): + # See the specification for details. + chomping = None + increment = None + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + elif ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected chomping or indentation indicators, but found %r" + % ch, self.get_mark()) + return chomping, increment + + def scan_block_scalar_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected a comment or a line break, but found %r" % ch, + self.get_mark()) + self.scan_line_break() + + def scan_block_scalar_indentation(self): + # See the specification for details. + chunks = [] + max_indent = 0 + end_mark = self.get_mark() + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() != ' ': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + else: + self.forward() + if self.column > max_indent: + max_indent = self.column + return chunks, max_indent, end_mark + + def scan_block_scalar_breaks(self, indent): + # See the specification for details. + chunks = [] + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + while self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + return chunks, end_mark + + def scan_flow_scalar(self, style): + # See the specification for details. + # Note that we loose indentation rules for quoted scalars. Quoted + # scalars don't need to adhere indentation because " and ' clearly + # mark the beginning and the end of them. Therefore we are less + # restrictive then the specification requires. We only need to check + # that document separators are not included in scalars. + if style == '"': + double = True + else: + double = False + chunks = [] + start_mark = self.get_mark() + quote = self.peek() + self.forward() + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + while self.peek() != quote: + chunks.extend(self.scan_flow_scalar_spaces(double, start_mark)) + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + self.forward() + end_mark = self.get_mark() + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + ESCAPE_REPLACEMENTS = { + '0': '\0', + 'a': '\x07', + 'b': '\x08', + 't': '\x09', + '\t': '\x09', + 'n': '\x0A', + 'v': '\x0B', + 'f': '\x0C', + 'r': '\x0D', + 'e': '\x1B', + ' ': '\x20', + '\"': '\"', + '\\': '\\', + '/': '/', + 'N': '\x85', + '_': '\xA0', + 'L': '\u2028', + 'P': '\u2029', + } + + ESCAPE_CODES = { + 'x': 2, + 'u': 4, + 'U': 8, + } + + def scan_flow_scalar_non_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + length = 0 + while self.peek(length) not in '\'\"\\\0 \t\r\n\x85\u2028\u2029': + length += 1 + if length: + chunks.append(self.prefix(length)) + self.forward(length) + ch = self.peek() + if not double and ch == '\'' and self.peek(1) == '\'': + chunks.append('\'') + self.forward(2) + elif (double and ch == '\'') or (not double and ch in '\"\\'): + chunks.append(ch) + self.forward() + elif double and ch == '\\': + self.forward() + ch = self.peek() + if ch in self.ESCAPE_REPLACEMENTS: + chunks.append(self.ESCAPE_REPLACEMENTS[ch]) + self.forward() + elif ch in self.ESCAPE_CODES: + length = self.ESCAPE_CODES[ch] + self.forward() + for k in range(length): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "expected escape sequence of %d hexadecimal numbers, but found %r" % + (length, self.peek(k)), self.get_mark()) + code = int(self.prefix(length), 16) + chunks.append(chr(code)) + self.forward(length) + elif ch in '\r\n\x85\u2028\u2029': + self.scan_line_break() + chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) + else: + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "found unknown escape character %r" % ch, self.get_mark()) + else: + return chunks + + def scan_flow_scalar_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + length = 0 + while self.peek(length) in ' \t': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch == '\0': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected end of stream", self.get_mark()) + elif ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + breaks = self.scan_flow_scalar_breaks(double, start_mark) + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + else: + chunks.append(whitespaces) + return chunks + + def scan_flow_scalar_breaks(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + # Instead of checking indentation, we check for document + # separators. + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected document separator", self.get_mark()) + while self.peek() in ' \t': + self.forward() + if self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + else: + return chunks + + def scan_plain(self): + # See the specification for details. + # We add an additional restriction for the flow context: + # plain scalars in the flow context cannot contain ',' or '?'. + # We also keep track of the `allow_simple_key` flag here. + # Indentation rules are loosed for the flow context. + chunks = [] + start_mark = self.get_mark() + end_mark = start_mark + indent = self.indent+1 + # We allow zero indentation for scalars, but then we need to check for + # document separators at the beginning of the line. + #if indent == 0: + # indent = 1 + spaces = [] + while True: + length = 0 + if self.peek() == '#': + break + while True: + ch = self.peek(length) + if ch in '\0 \t\r\n\x85\u2028\u2029' \ + or (ch == ':' and + self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' + + (u',[]{}' if self.flow_level else u''))\ + or (self.flow_level and ch in ',?[]{}'): + break + length += 1 + if length == 0: + break + self.allow_simple_key = False + chunks.extend(spaces) + chunks.append(self.prefix(length)) + self.forward(length) + end_mark = self.get_mark() + spaces = self.scan_plain_spaces(indent, start_mark) + if not spaces or self.peek() == '#' \ + or (not self.flow_level and self.column < indent): + break + return ScalarToken(''.join(chunks), True, start_mark, end_mark) + + def scan_plain_spaces(self, indent, start_mark): + # See the specification for details. + # The specification is really confusing about tabs in plain scalars. + # We just forbid them completely. Do not use tabs in YAML! + chunks = [] + length = 0 + while self.peek(length) in ' ': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + self.allow_simple_key = True + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + breaks = [] + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() == ' ': + self.forward() + else: + breaks.append(self.scan_line_break()) + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + elif whitespaces: + chunks.append(whitespaces) + return chunks + + def scan_tag_handle(self, name, start_mark): + # See the specification for details. + # For some strange reasons, the specification does not allow '_' in + # tag handles. I have allowed it anyway. + ch = self.peek() + if ch != '!': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length = 1 + ch = self.peek(length) + if ch != ' ': + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if ch != '!': + self.forward(length) + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length += 1 + value = self.prefix(length) + self.forward(length) + return value + + def scan_tag_uri(self, name, start_mark): + # See the specification for details. + # Note: we do not check if URI is well-formed. + chunks = [] + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.!~*\'()[]%': + if ch == '%': + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + chunks.append(self.scan_uri_escapes(name, start_mark)) + else: + length += 1 + ch = self.peek(length) + if length: + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + if not chunks: + raise ScannerError("while parsing a %s" % name, start_mark, + "expected URI, but found %r" % ch, self.get_mark()) + return ''.join(chunks) + + def scan_uri_escapes(self, name, start_mark): + # See the specification for details. + codes = [] + mark = self.get_mark() + while self.peek() == '%': + self.forward() + for k in range(2): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected URI escape sequence of 2 hexadecimal numbers, but found %r" + % self.peek(k), self.get_mark()) + codes.append(int(self.prefix(2), 16)) + self.forward(2) + try: + value = bytes(codes).decode('utf-8') + except UnicodeDecodeError as exc: + raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark) + return value + + def scan_line_break(self): + # Transforms: + # '\r\n' : '\n' + # '\r' : '\n' + # '\n' : '\n' + # '\x85' : '\n' + # '\u2028' : '\u2028' + # '\u2029 : '\u2029' + # default : '' + ch = self.peek() + if ch in '\r\n\x85': + if self.prefix(2) == '\r\n': + self.forward(2) + else: + self.forward() + return '\n' + elif ch in '\u2028\u2029': + self.forward() + return ch + return '' diff --git a/testing/originalnodep/yaml/serializer.py b/testing/originalnodep/yaml/serializer.py new file mode 100644 index 00000000..fe911e67 --- /dev/null +++ b/testing/originalnodep/yaml/serializer.py @@ -0,0 +1,111 @@ + +__all__ = ['Serializer', 'SerializerError'] + +from .error import YAMLError +from .events import * +from .nodes import * + +class SerializerError(YAMLError): + pass + +class Serializer: + + ANCHOR_TEMPLATE = 'id%03d' + + def __init__(self, encoding=None, + explicit_start=None, explicit_end=None, version=None, tags=None): + self.use_encoding = encoding + self.use_explicit_start = explicit_start + self.use_explicit_end = explicit_end + self.use_version = version + self.use_tags = tags + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + self.closed = None + + def open(self): + if self.closed is None: + self.emit(StreamStartEvent(encoding=self.use_encoding)) + self.closed = False + elif self.closed: + raise SerializerError("serializer is closed") + else: + raise SerializerError("serializer is already opened") + + def close(self): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif not self.closed: + self.emit(StreamEndEvent()) + self.closed = True + + #def __del__(self): + # self.close() + + def serialize(self, node): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif self.closed: + raise SerializerError("serializer is closed") + self.emit(DocumentStartEvent(explicit=self.use_explicit_start, + version=self.use_version, tags=self.use_tags)) + self.anchor_node(node) + self.serialize_node(node, None, None) + self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + + def anchor_node(self, node): + if node in self.anchors: + if self.anchors[node] is None: + self.anchors[node] = self.generate_anchor(node) + else: + self.anchors[node] = None + if isinstance(node, SequenceNode): + for item in node.value: + self.anchor_node(item) + elif isinstance(node, MappingNode): + for key, value in node.value: + self.anchor_node(key) + self.anchor_node(value) + + def generate_anchor(self, node): + self.last_anchor_id += 1 + return self.ANCHOR_TEMPLATE % self.last_anchor_id + + def serialize_node(self, node, parent, index): + alias = self.anchors[node] + if node in self.serialized_nodes: + self.emit(AliasEvent(alias)) + else: + self.serialized_nodes[node] = True + self.descend_resolver(parent, index) + if isinstance(node, ScalarNode): + detected_tag = self.resolve(ScalarNode, node.value, (True, False)) + default_tag = self.resolve(ScalarNode, node.value, (False, True)) + implicit = (node.tag == detected_tag), (node.tag == default_tag) + self.emit(ScalarEvent(alias, node.tag, implicit, node.value, + style=node.style)) + elif isinstance(node, SequenceNode): + implicit = (node.tag + == self.resolve(SequenceNode, node.value, True)) + self.emit(SequenceStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + index = 0 + for item in node.value: + self.serialize_node(item, node, index) + index += 1 + self.emit(SequenceEndEvent()) + elif isinstance(node, MappingNode): + implicit = (node.tag + == self.resolve(MappingNode, node.value, True)) + self.emit(MappingStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + for key, value in node.value: + self.serialize_node(key, node, None) + self.serialize_node(value, node, key) + self.emit(MappingEndEvent()) + self.ascend_resolver() + diff --git a/testing/originalnodep/yaml/tokens.py b/testing/originalnodep/yaml/tokens.py new file mode 100644 index 00000000..4d0b48a3 --- /dev/null +++ b/testing/originalnodep/yaml/tokens.py @@ -0,0 +1,104 @@ + +class Token(object): + def __init__(self, start_mark, end_mark): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in self.__dict__ + if not key.endswith('_mark')] + attributes.sort() + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +#class BOMToken(Token): +# id = '' + +class DirectiveToken(Token): + id = '' + def __init__(self, name, value, start_mark, end_mark): + self.name = name + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class DocumentStartToken(Token): + id = '' + +class DocumentEndToken(Token): + id = '' + +class StreamStartToken(Token): + id = '' + def __init__(self, start_mark=None, end_mark=None, + encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndToken(Token): + id = '' + +class BlockSequenceStartToken(Token): + id = '' + +class BlockMappingStartToken(Token): + id = '' + +class BlockEndToken(Token): + id = '' + +class FlowSequenceStartToken(Token): + id = '[' + +class FlowMappingStartToken(Token): + id = '{' + +class FlowSequenceEndToken(Token): + id = ']' + +class FlowMappingEndToken(Token): + id = '}' + +class KeyToken(Token): + id = '?' + +class ValueToken(Token): + id = ':' + +class BlockEntryToken(Token): + id = '-' + +class FlowEntryToken(Token): + id = ',' + +class AliasToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class AnchorToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class TagToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class ScalarToken(Token): + id = '' + def __init__(self, value, plain, start_mark, end_mark, style=None): + self.value = value + self.plain = plain + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + diff --git a/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/INSTALLER b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/LICENSE b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/LICENSE new file mode 100644 index 00000000..2f1b8e15 --- /dev/null +++ b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017-2021 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/METADATA b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/METADATA new file mode 100644 index 00000000..c8905983 --- /dev/null +++ b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/METADATA @@ -0,0 +1,46 @@ +Metadata-Version: 2.1 +Name: PyYAML +Version: 6.0.1 +Summary: YAML parser and emitter for Python +Home-page: https://pyyaml.org/ +Download-URL: https://pypi.org/project/PyYAML/ +Author: Kirill Simonov +Author-email: xi@resolvent.net +License: MIT +Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues +Project-URL: CI, https://github.com/yaml/pyyaml/actions +Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation +Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core +Project-URL: Source Code, https://github.com/yaml/pyyaml +Platform: Any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Markup +Requires-Python: >=3.6 +License-File: LICENSE + +YAML is a data serialization format designed for human readability +and interaction with scripting languages. PyYAML is a YAML parser +and emitter for Python. + +PyYAML features a complete YAML 1.1 parser, Unicode support, pickle +support, capable extension API, and sensible error messages. PyYAML +supports standard YAML tags and provides Python-specific tags that +allow to represent an arbitrary Python object. + +PyYAML is applicable for a broad range of tasks from complex +configuration files to object serialization and persistence. diff --git a/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/RECORD b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/RECORD new file mode 100644 index 00000000..769558ba --- /dev/null +++ b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/RECORD @@ -0,0 +1,44 @@ +PyYAML-6.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +PyYAML-6.0.1.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101 +PyYAML-6.0.1.dist-info/METADATA,sha256=UNNF8-SzzwOKXVo-kV5lXUGH2_wDWMBmGxqISpp5HQk,2058 +PyYAML-6.0.1.dist-info/RECORD,, +PyYAML-6.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +PyYAML-6.0.1.dist-info/WHEEL,sha256=FCrbbeH_Uuw2ZMaB8nW-JE7XeUWVfF-XtWcVJYU0Zm8,110 +PyYAML-6.0.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11 +_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402 +_yaml/__pycache__/__init__.cpython-312.pyc,, +yaml/__init__.py,sha256=bhl05qSeO-1ZxlSRjGrvl2m9nrXb1n9-GQatTN0Mrqc,12311 +yaml/__pycache__/__init__.cpython-312.pyc,, +yaml/__pycache__/composer.cpython-312.pyc,, +yaml/__pycache__/constructor.cpython-312.pyc,, +yaml/__pycache__/cyaml.cpython-312.pyc,, +yaml/__pycache__/dumper.cpython-312.pyc,, +yaml/__pycache__/emitter.cpython-312.pyc,, +yaml/__pycache__/error.cpython-312.pyc,, +yaml/__pycache__/events.cpython-312.pyc,, +yaml/__pycache__/loader.cpython-312.pyc,, +yaml/__pycache__/nodes.cpython-312.pyc,, +yaml/__pycache__/parser.cpython-312.pyc,, +yaml/__pycache__/reader.cpython-312.pyc,, +yaml/__pycache__/representer.cpython-312.pyc,, +yaml/__pycache__/resolver.cpython-312.pyc,, +yaml/__pycache__/scanner.cpython-312.pyc,, +yaml/__pycache__/serializer.cpython-312.pyc,, +yaml/__pycache__/tokens.cpython-312.pyc,, +yaml/_yaml.cpython-312-darwin.so,sha256=zLYFDcq5DNWEltMIpJVcuFQ4jRWM4bIJADV_pT4O5wM,361928 +yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883 +yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639 +yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851 +yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837 +yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006 +yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533 +yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445 +yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061 +yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440 +yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495 +yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794 +yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190 +yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004 +yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279 +yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165 +yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573 diff --git a/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/REQUESTED b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/WHEEL b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/WHEEL new file mode 100644 index 00000000..2e30befe --- /dev/null +++ b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.41.2) +Root-Is-Purelib: false +Tag: cp312-cp312-macosx_11_0_arm64 + diff --git a/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/top_level.txt b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/top_level.txt new file mode 100644 index 00000000..e6475e91 --- /dev/null +++ b/testing/prajintry/pacakges/PyYAML-6.0.1.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_yaml +yaml diff --git a/testing/prajintry/pacakges/_yaml/__init__.py b/testing/prajintry/pacakges/_yaml/__init__.py new file mode 100644 index 00000000..7baa8c4b --- /dev/null +++ b/testing/prajintry/pacakges/_yaml/__init__.py @@ -0,0 +1,33 @@ +# This is a stub package designed to roughly emulate the _yaml +# extension module, which previously existed as a standalone module +# and has been moved into the `yaml` package namespace. +# It does not perfectly mimic its old counterpart, but should get +# close enough for anyone who's relying on it even when they shouldn't. +import yaml + +# in some circumstances, the yaml module we imoprted may be from a different version, so we need +# to tread carefully when poking at it here (it may not have the attributes we expect) +if not getattr(yaml, '__with_libyaml__', False): + from sys import version_info + + exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError + raise exc("No module named '_yaml'") +else: + from yaml._yaml import * + import warnings + warnings.warn( + 'The _yaml extension module is now located at yaml._yaml' + ' and its location is subject to change. To use the' + ' LibYAML-based parser and emitter, import from `yaml`:' + ' `from yaml import CLoader as Loader, CDumper as Dumper`.', + DeprecationWarning + ) + del warnings + # Don't `del yaml` here because yaml is actually an existing + # namespace member of _yaml. + +__name__ = '_yaml' +# If the module is top-level (i.e. not a part of any specific package) +# then the attribute should be set to ''. +# https://docs.python.org/3.8/library/types.html +__package__ = '' diff --git a/testing/prajintry/pacakges/yaml/__init__.py b/testing/prajintry/pacakges/yaml/__init__.py new file mode 100644 index 00000000..82493619 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/__init__.py @@ -0,0 +1,390 @@ + +from .error import * + +from .tokens import * +from .events import * +from .nodes import * + +from .loader import * +from .dumper import * + +__version__ = '6.0.1' +try: + from .cyaml import * + __with_libyaml__ = True +except ImportError: + __with_libyaml__ = False + +import io + +#------------------------------------------------------------------------------ +# XXX "Warnings control" is now deprecated. Leaving in the API function to not +# break code that uses it. +#------------------------------------------------------------------------------ +def warnings(settings=None): + if settings is None: + return {} + +#------------------------------------------------------------------------------ +def scan(stream, Loader=Loader): + """ + Scan a YAML stream and produce scanning tokens. + """ + loader = Loader(stream) + try: + while loader.check_token(): + yield loader.get_token() + finally: + loader.dispose() + +def parse(stream, Loader=Loader): + """ + Parse a YAML stream and produce parsing events. + """ + loader = Loader(stream) + try: + while loader.check_event(): + yield loader.get_event() + finally: + loader.dispose() + +def compose(stream, Loader=Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding representation tree. + """ + loader = Loader(stream) + try: + return loader.get_single_node() + finally: + loader.dispose() + +def compose_all(stream, Loader=Loader): + """ + Parse all YAML documents in a stream + and produce corresponding representation trees. + """ + loader = Loader(stream) + try: + while loader.check_node(): + yield loader.get_node() + finally: + loader.dispose() + +def load(stream, Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + """ + loader = Loader(stream) + try: + return loader.get_single_data() + finally: + loader.dispose() + +def load_all(stream, Loader): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + """ + loader = Loader(stream) + try: + while loader.check_data(): + yield loader.get_data() + finally: + loader.dispose() + +def full_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load(stream, FullLoader) + +def full_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load_all(stream, FullLoader) + +def safe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load(stream, SafeLoader) + +def safe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load_all(stream, SafeLoader) + +def unsafe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load(stream, UnsafeLoader) + +def unsafe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load_all(stream, UnsafeLoader) + +def emit(events, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + """ + Emit YAML parsing events into a stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + stream = io.StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + try: + for event in events: + dumper.emit(event) + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize_all(nodes, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + """ + Serialize a sequence of representation trees into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end) + try: + dumper.open() + for node in nodes: + dumper.serialize(node) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize(node, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a representation tree into a YAML stream. + If stream is None, return the produced string instead. + """ + return serialize_all([node], stream, Dumper=Dumper, **kwds) + +def dump_all(documents, stream=None, Dumper=Dumper, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + """ + Serialize a sequence of Python objects into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, default_style=default_style, + default_flow_style=default_flow_style, + canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys) + try: + dumper.open() + for data in documents: + dumper.represent(data) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def dump(data, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a Python object into a YAML stream. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=Dumper, **kwds) + +def safe_dump_all(documents, stream=None, **kwds): + """ + Serialize a sequence of Python objects into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all(documents, stream, Dumper=SafeDumper, **kwds) + +def safe_dump(data, stream=None, **kwds): + """ + Serialize a Python object into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=SafeDumper, **kwds) + +def add_implicit_resolver(tag, regexp, first=None, + Loader=None, Dumper=Dumper): + """ + Add an implicit scalar detector. + If an implicit scalar value matches the given regexp, + the corresponding tag is assigned to the scalar. + first is a sequence of possible initial characters or None. + """ + if Loader is None: + loader.Loader.add_implicit_resolver(tag, regexp, first) + loader.FullLoader.add_implicit_resolver(tag, regexp, first) + loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first) + else: + Loader.add_implicit_resolver(tag, regexp, first) + Dumper.add_implicit_resolver(tag, regexp, first) + +def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper): + """ + Add a path based resolver for the given tag. + A path is a list of keys that forms a path + to a node in the representation tree. + Keys can be string values, integers, or None. + """ + if Loader is None: + loader.Loader.add_path_resolver(tag, path, kind) + loader.FullLoader.add_path_resolver(tag, path, kind) + loader.UnsafeLoader.add_path_resolver(tag, path, kind) + else: + Loader.add_path_resolver(tag, path, kind) + Dumper.add_path_resolver(tag, path, kind) + +def add_constructor(tag, constructor, Loader=None): + """ + Add a constructor for the given tag. + Constructor is a function that accepts a Loader instance + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_constructor(tag, constructor) + loader.FullLoader.add_constructor(tag, constructor) + loader.UnsafeLoader.add_constructor(tag, constructor) + else: + Loader.add_constructor(tag, constructor) + +def add_multi_constructor(tag_prefix, multi_constructor, Loader=None): + """ + Add a multi-constructor for the given tag prefix. + Multi-constructor is called for a node if its tag starts with tag_prefix. + Multi-constructor accepts a Loader instance, a tag suffix, + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_multi_constructor(tag_prefix, multi_constructor) + loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor) + loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor) + else: + Loader.add_multi_constructor(tag_prefix, multi_constructor) + +def add_representer(data_type, representer, Dumper=Dumper): + """ + Add a representer for the given type. + Representer is a function accepting a Dumper instance + and an instance of the given data type + and producing the corresponding representation node. + """ + Dumper.add_representer(data_type, representer) + +def add_multi_representer(data_type, multi_representer, Dumper=Dumper): + """ + Add a representer for the given type. + Multi-representer is a function accepting a Dumper instance + and an instance of the given data type or subtype + and producing the corresponding representation node. + """ + Dumper.add_multi_representer(data_type, multi_representer) + +class YAMLObjectMetaclass(type): + """ + The metaclass for YAMLObject. + """ + def __init__(cls, name, bases, kwds): + super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) + if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: + if isinstance(cls.yaml_loader, list): + for loader in cls.yaml_loader: + loader.add_constructor(cls.yaml_tag, cls.from_yaml) + else: + cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) + + cls.yaml_dumper.add_representer(cls, cls.to_yaml) + +class YAMLObject(metaclass=YAMLObjectMetaclass): + """ + An object that can dump itself to a YAML stream + and load itself from a YAML stream. + """ + + __slots__ = () # no direct instantiation, so allow immutable subclasses + + yaml_loader = [Loader, FullLoader, UnsafeLoader] + yaml_dumper = Dumper + + yaml_tag = None + yaml_flow_style = None + + @classmethod + def from_yaml(cls, loader, node): + """ + Convert a representation node to a Python object. + """ + return loader.construct_yaml_object(node, cls) + + @classmethod + def to_yaml(cls, dumper, data): + """ + Convert a Python object to a representation node. + """ + return dumper.represent_yaml_object(cls.yaml_tag, data, cls, + flow_style=cls.yaml_flow_style) + diff --git a/testing/prajintry/pacakges/yaml/composer.py b/testing/prajintry/pacakges/yaml/composer.py new file mode 100644 index 00000000..6d15cb40 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/composer.py @@ -0,0 +1,139 @@ + +__all__ = ['Composer', 'ComposerError'] + +from .error import MarkedYAMLError +from .events import * +from .nodes import * + +class ComposerError(MarkedYAMLError): + pass + +class Composer: + + def __init__(self): + self.anchors = {} + + def check_node(self): + # Drop the STREAM-START event. + if self.check_event(StreamStartEvent): + self.get_event() + + # If there are more documents available? + return not self.check_event(StreamEndEvent) + + def get_node(self): + # Get the root node of the next document. + if not self.check_event(StreamEndEvent): + return self.compose_document() + + def get_single_node(self): + # Drop the STREAM-START event. + self.get_event() + + # Compose a document if the stream is not empty. + document = None + if not self.check_event(StreamEndEvent): + document = self.compose_document() + + # Ensure that the stream contains no more documents. + if not self.check_event(StreamEndEvent): + event = self.get_event() + raise ComposerError("expected a single document in the stream", + document.start_mark, "but found another document", + event.start_mark) + + # Drop the STREAM-END event. + self.get_event() + + return document + + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + + # Compose the root node. + node = self.compose_node(None, None) + + # Drop the DOCUMENT-END event. + self.get_event() + + self.anchors = {} + return node + + def compose_node(self, parent, index): + if self.check_event(AliasEvent): + event = self.get_event() + anchor = event.anchor + if anchor not in self.anchors: + raise ComposerError(None, None, "found undefined alias %r" + % anchor, event.start_mark) + return self.anchors[anchor] + event = self.peek_event() + anchor = event.anchor + if anchor is not None: + if anchor in self.anchors: + raise ComposerError("found duplicate anchor %r; first occurrence" + % anchor, self.anchors[anchor].start_mark, + "second occurrence", event.start_mark) + self.descend_resolver(parent, index) + if self.check_event(ScalarEvent): + node = self.compose_scalar_node(anchor) + elif self.check_event(SequenceStartEvent): + node = self.compose_sequence_node(anchor) + elif self.check_event(MappingStartEvent): + node = self.compose_mapping_node(anchor) + self.ascend_resolver() + return node + + def compose_scalar_node(self, anchor): + event = self.get_event() + tag = event.tag + if tag is None or tag == '!': + tag = self.resolve(ScalarNode, event.value, event.implicit) + node = ScalarNode(tag, event.value, + event.start_mark, event.end_mark, style=event.style) + if anchor is not None: + self.anchors[anchor] = node + return node + + def compose_sequence_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(SequenceNode, None, start_event.implicit) + node = SequenceNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + index = 0 + while not self.check_event(SequenceEndEvent): + node.value.append(self.compose_node(node, index)) + index += 1 + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + + def compose_mapping_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(MappingNode, None, start_event.implicit) + node = MappingNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + while not self.check_event(MappingEndEvent): + #key_event = self.peek_event() + item_key = self.compose_node(node, None) + #if item_key in node.value: + # raise ComposerError("while composing a mapping", start_event.start_mark, + # "found duplicate key", key_event.start_mark) + item_value = self.compose_node(node, item_key) + #node.value[item_key] = item_value + node.value.append((item_key, item_value)) + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + diff --git a/testing/prajintry/pacakges/yaml/constructor.py b/testing/prajintry/pacakges/yaml/constructor.py new file mode 100644 index 00000000..619acd30 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/constructor.py @@ -0,0 +1,748 @@ + +__all__ = [ + 'BaseConstructor', + 'SafeConstructor', + 'FullConstructor', + 'UnsafeConstructor', + 'Constructor', + 'ConstructorError' +] + +from .error import * +from .nodes import * + +import collections.abc, datetime, base64, binascii, re, sys, types + +class ConstructorError(MarkedYAMLError): + pass + +class BaseConstructor: + + yaml_constructors = {} + yaml_multi_constructors = {} + + def __init__(self): + self.constructed_objects = {} + self.recursive_objects = {} + self.state_generators = [] + self.deep_construct = False + + def check_data(self): + # If there are more documents available? + return self.check_node() + + def check_state_key(self, key): + """Block special attributes/methods from being set in a newly created + object, to prevent user-controlled methods from being called during + deserialization""" + if self.get_state_keys_blacklist_regexp().match(key): + raise ConstructorError(None, None, + "blacklisted key '%s' in instance state found" % (key,), None) + + def get_data(self): + # Construct and return the next document. + if self.check_node(): + return self.construct_document(self.get_node()) + + def get_single_data(self): + # Ensure that the stream contains a single document and construct it. + node = self.get_single_node() + if node is not None: + return self.construct_document(node) + return None + + def construct_document(self, node): + data = self.construct_object(node) + while self.state_generators: + state_generators = self.state_generators + self.state_generators = [] + for generator in state_generators: + for dummy in generator: + pass + self.constructed_objects = {} + self.recursive_objects = {} + self.deep_construct = False + return data + + def construct_object(self, node, deep=False): + if node in self.constructed_objects: + return self.constructed_objects[node] + if deep: + old_deep = self.deep_construct + self.deep_construct = True + if node in self.recursive_objects: + raise ConstructorError(None, None, + "found unconstructable recursive node", node.start_mark) + self.recursive_objects[node] = None + constructor = None + tag_suffix = None + if node.tag in self.yaml_constructors: + constructor = self.yaml_constructors[node.tag] + else: + for tag_prefix in self.yaml_multi_constructors: + if tag_prefix is not None and node.tag.startswith(tag_prefix): + tag_suffix = node.tag[len(tag_prefix):] + constructor = self.yaml_multi_constructors[tag_prefix] + break + else: + if None in self.yaml_multi_constructors: + tag_suffix = node.tag + constructor = self.yaml_multi_constructors[None] + elif None in self.yaml_constructors: + constructor = self.yaml_constructors[None] + elif isinstance(node, ScalarNode): + constructor = self.__class__.construct_scalar + elif isinstance(node, SequenceNode): + constructor = self.__class__.construct_sequence + elif isinstance(node, MappingNode): + constructor = self.__class__.construct_mapping + if tag_suffix is None: + data = constructor(self, node) + else: + data = constructor(self, tag_suffix, node) + if isinstance(data, types.GeneratorType): + generator = data + data = next(generator) + if self.deep_construct: + for dummy in generator: + pass + else: + self.state_generators.append(generator) + self.constructed_objects[node] = data + del self.recursive_objects[node] + if deep: + self.deep_construct = old_deep + return data + + def construct_scalar(self, node): + if not isinstance(node, ScalarNode): + raise ConstructorError(None, None, + "expected a scalar node, but found %s" % node.id, + node.start_mark) + return node.value + + def construct_sequence(self, node, deep=False): + if not isinstance(node, SequenceNode): + raise ConstructorError(None, None, + "expected a sequence node, but found %s" % node.id, + node.start_mark) + return [self.construct_object(child, deep=deep) + for child in node.value] + + def construct_mapping(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + mapping = {} + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, collections.abc.Hashable): + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unhashable key", key_node.start_mark) + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + + def construct_pairs(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + pairs = [] + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + value = self.construct_object(value_node, deep=deep) + pairs.append((key, value)) + return pairs + + @classmethod + def add_constructor(cls, tag, constructor): + if not 'yaml_constructors' in cls.__dict__: + cls.yaml_constructors = cls.yaml_constructors.copy() + cls.yaml_constructors[tag] = constructor + + @classmethod + def add_multi_constructor(cls, tag_prefix, multi_constructor): + if not 'yaml_multi_constructors' in cls.__dict__: + cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() + cls.yaml_multi_constructors[tag_prefix] = multi_constructor + +class SafeConstructor(BaseConstructor): + + def construct_scalar(self, node): + if isinstance(node, MappingNode): + for key_node, value_node in node.value: + if key_node.tag == 'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return super().construct_scalar(node) + + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == 'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError("while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark) + elif key_node.tag == 'tag:yaml.org,2002:value': + key_node.tag = 'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value + + def construct_mapping(self, node, deep=False): + if isinstance(node, MappingNode): + self.flatten_mapping(node) + return super().construct_mapping(node, deep=deep) + + def construct_yaml_null(self, node): + self.construct_scalar(node) + return None + + bool_values = { + 'yes': True, + 'no': False, + 'true': True, + 'false': False, + 'on': True, + 'off': False, + } + + def construct_yaml_bool(self, node): + value = self.construct_scalar(node) + return self.bool_values[value.lower()] + + def construct_yaml_int(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '') + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + elif value.startswith('0b'): + return sign*int(value[2:], 2) + elif value.startswith('0x'): + return sign*int(value[2:], 16) + elif value[0] == '0': + return sign*int(value, 8) + elif ':' in value: + digits = [int(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value) + + inf_value = 1e300 + while inf_value != inf_value*inf_value: + inf_value *= inf_value + nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). + + def construct_yaml_float(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '').lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '.inf': + return sign*self.inf_value + elif value == '.nan': + return self.nan_value + elif ':' in value: + digits = [float(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0.0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*float(value) + + def construct_yaml_binary(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + timestamp_regexp = re.compile( + r'''^(?P[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)?$''', re.X) + + def construct_yaml_timestamp(self, node): + value = self.construct_scalar(node) + match = self.timestamp_regexp.match(node.value) + values = match.groupdict() + year = int(values['year']) + month = int(values['month']) + day = int(values['day']) + if not values['hour']: + return datetime.date(year, month, day) + hour = int(values['hour']) + minute = int(values['minute']) + second = int(values['second']) + fraction = 0 + tzinfo = None + if values['fraction']: + fraction = values['fraction'][:6] + while len(fraction) < 6: + fraction += '0' + fraction = int(fraction) + if values['tz_sign']: + tz_hour = int(values['tz_hour']) + tz_minute = int(values['tz_minute'] or 0) + delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) + if values['tz_sign'] == '-': + delta = -delta + tzinfo = datetime.timezone(delta) + elif values['tz']: + tzinfo = datetime.timezone.utc + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo) + + def construct_yaml_omap(self, node): + # Note: we do not check for duplicate keys, because it's too + # CPU-expensive. + omap = [] + yield omap + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + omap.append((key, value)) + + def construct_yaml_pairs(self, node): + # Note: the same code as `construct_yaml_omap`. + pairs = [] + yield pairs + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + pairs.append((key, value)) + + def construct_yaml_set(self, node): + data = set() + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_str(self, node): + return self.construct_scalar(node) + + def construct_yaml_seq(self, node): + data = [] + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = {} + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_object(self, node, cls): + data = cls.__new__(cls) + yield data + if hasattr(data, '__setstate__'): + state = self.construct_mapping(node, deep=True) + data.__setstate__(state) + else: + state = self.construct_mapping(node) + data.__dict__.update(state) + + def construct_undefined(self, node): + raise ConstructorError(None, None, + "could not determine a constructor for the tag %r" % node.tag, + node.start_mark) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:null', + SafeConstructor.construct_yaml_null) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:bool', + SafeConstructor.construct_yaml_bool) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:int', + SafeConstructor.construct_yaml_int) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:float', + SafeConstructor.construct_yaml_float) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:binary', + SafeConstructor.construct_yaml_binary) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:timestamp', + SafeConstructor.construct_yaml_timestamp) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:omap', + SafeConstructor.construct_yaml_omap) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:pairs', + SafeConstructor.construct_yaml_pairs) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:set', + SafeConstructor.construct_yaml_set) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:str', + SafeConstructor.construct_yaml_str) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:seq', + SafeConstructor.construct_yaml_seq) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:map', + SafeConstructor.construct_yaml_map) + +SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + +class FullConstructor(SafeConstructor): + # 'extend' is blacklisted because it is used by + # construct_python_object_apply to add `listitems` to a newly generate + # python instance + def get_state_keys_blacklist(self): + return ['^extend$', '^__.*__$'] + + def get_state_keys_blacklist_regexp(self): + if not hasattr(self, 'state_keys_blacklist_regexp'): + self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')') + return self.state_keys_blacklist_regexp + + def construct_python_str(self, node): + return self.construct_scalar(node) + + def construct_python_unicode(self, node): + return self.construct_scalar(node) + + def construct_python_bytes(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + def construct_python_long(self, node): + return self.construct_yaml_int(node) + + def construct_python_complex(self, node): + return complex(self.construct_scalar(node)) + + def construct_python_tuple(self, node): + return tuple(self.construct_sequence(node)) + + def find_python_module(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python module", mark, + "expected non-empty name appended to the tag", mark) + if unsafe: + try: + __import__(name) + except ImportError as exc: + raise ConstructorError("while constructing a Python module", mark, + "cannot find module %r (%s)" % (name, exc), mark) + if name not in sys.modules: + raise ConstructorError("while constructing a Python module", mark, + "module %r is not imported" % name, mark) + return sys.modules[name] + + def find_python_name(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python object", mark, + "expected non-empty name appended to the tag", mark) + if '.' in name: + module_name, object_name = name.rsplit('.', 1) + else: + module_name = 'builtins' + object_name = name + if unsafe: + try: + __import__(module_name) + except ImportError as exc: + raise ConstructorError("while constructing a Python object", mark, + "cannot find module %r (%s)" % (module_name, exc), mark) + if module_name not in sys.modules: + raise ConstructorError("while constructing a Python object", mark, + "module %r is not imported" % module_name, mark) + module = sys.modules[module_name] + if not hasattr(module, object_name): + raise ConstructorError("while constructing a Python object", mark, + "cannot find %r in the module %r" + % (object_name, module.__name__), mark) + return getattr(module, object_name) + + def construct_python_name(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python name", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_name(suffix, node.start_mark) + + def construct_python_module(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python module", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_module(suffix, node.start_mark) + + def make_python_instance(self, suffix, node, + args=None, kwds=None, newobj=False, unsafe=False): + if not args: + args = [] + if not kwds: + kwds = {} + cls = self.find_python_name(suffix, node.start_mark) + if not (unsafe or isinstance(cls, type)): + raise ConstructorError("while constructing a Python instance", node.start_mark, + "expected a class, but found %r" % type(cls), + node.start_mark) + if newobj and isinstance(cls, type): + return cls.__new__(cls, *args, **kwds) + else: + return cls(*args, **kwds) + + def set_python_instance_state(self, instance, state, unsafe=False): + if hasattr(instance, '__setstate__'): + instance.__setstate__(state) + else: + slotstate = {} + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + if hasattr(instance, '__dict__'): + if not unsafe and state: + for key in state.keys(): + self.check_state_key(key) + instance.__dict__.update(state) + elif state: + slotstate.update(state) + for key, value in slotstate.items(): + if not unsafe: + self.check_state_key(key) + setattr(instance, key, value) + + def construct_python_object(self, suffix, node): + # Format: + # !!python/object:module.name { ... state ... } + instance = self.make_python_instance(suffix, node, newobj=True) + yield instance + deep = hasattr(instance, '__setstate__') + state = self.construct_mapping(node, deep=deep) + self.set_python_instance_state(instance, state) + + def construct_python_object_apply(self, suffix, node, newobj=False): + # Format: + # !!python/object/apply # (or !!python/object/new) + # args: [ ... arguments ... ] + # kwds: { ... keywords ... } + # state: ... state ... + # listitems: [ ... listitems ... ] + # dictitems: { ... dictitems ... } + # or short format: + # !!python/object/apply [ ... arguments ... ] + # The difference between !!python/object/apply and !!python/object/new + # is how an object is created, check make_python_instance for details. + if isinstance(node, SequenceNode): + args = self.construct_sequence(node, deep=True) + kwds = {} + state = {} + listitems = [] + dictitems = {} + else: + value = self.construct_mapping(node, deep=True) + args = value.get('args', []) + kwds = value.get('kwds', {}) + state = value.get('state', {}) + listitems = value.get('listitems', []) + dictitems = value.get('dictitems', {}) + instance = self.make_python_instance(suffix, node, args, kwds, newobj) + if state: + self.set_python_instance_state(instance, state) + if listitems: + instance.extend(listitems) + if dictitems: + for key in dictitems: + instance[key] = dictitems[key] + return instance + + def construct_python_object_new(self, suffix, node): + return self.construct_python_object_apply(suffix, node, newobj=True) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/none', + FullConstructor.construct_yaml_null) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bool', + FullConstructor.construct_yaml_bool) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/str', + FullConstructor.construct_python_str) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/unicode', + FullConstructor.construct_python_unicode) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bytes', + FullConstructor.construct_python_bytes) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/int', + FullConstructor.construct_yaml_int) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/long', + FullConstructor.construct_python_long) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/float', + FullConstructor.construct_yaml_float) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/complex', + FullConstructor.construct_python_complex) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/list', + FullConstructor.construct_yaml_seq) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/tuple', + FullConstructor.construct_python_tuple) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/dict', + FullConstructor.construct_yaml_map) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/name:', + FullConstructor.construct_python_name) + +class UnsafeConstructor(FullConstructor): + + def find_python_module(self, name, mark): + return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True) + + def find_python_name(self, name, mark): + return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True) + + def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False): + return super(UnsafeConstructor, self).make_python_instance( + suffix, node, args, kwds, newobj, unsafe=True) + + def set_python_instance_state(self, instance, state): + return super(UnsafeConstructor, self).set_python_instance_state( + instance, state, unsafe=True) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/module:', + UnsafeConstructor.construct_python_module) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object:', + UnsafeConstructor.construct_python_object) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/new:', + UnsafeConstructor.construct_python_object_new) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/apply:', + UnsafeConstructor.construct_python_object_apply) + +# Constructor is same as UnsafeConstructor. Need to leave this in place in case +# people have extended it directly. +class Constructor(UnsafeConstructor): + pass diff --git a/testing/prajintry/pacakges/yaml/cyaml.py b/testing/prajintry/pacakges/yaml/cyaml.py new file mode 100644 index 00000000..0c213458 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/cyaml.py @@ -0,0 +1,101 @@ + +__all__ = [ + 'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader', + 'CBaseDumper', 'CSafeDumper', 'CDumper' +] + +from yaml._yaml import CParser, CEmitter + +from .constructor import * + +from .serializer import * +from .representer import * + +from .resolver import * + +class CBaseLoader(CParser, BaseConstructor, BaseResolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class CSafeLoader(CParser, SafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class CFullLoader(CParser, FullConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class CUnsafeLoader(CParser, UnsafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + UnsafeConstructor.__init__(self) + Resolver.__init__(self) + +class CLoader(CParser, Constructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + Constructor.__init__(self) + Resolver.__init__(self) + +class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CSafeDumper(CEmitter, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CDumper(CEmitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/testing/prajintry/pacakges/yaml/dumper.py b/testing/prajintry/pacakges/yaml/dumper.py new file mode 100644 index 00000000..6aadba55 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/dumper.py @@ -0,0 +1,62 @@ + +__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] + +from .emitter import * +from .serializer import * +from .representer import * +from .resolver import * + +class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class Dumper(Emitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/testing/prajintry/pacakges/yaml/emitter.py b/testing/prajintry/pacakges/yaml/emitter.py new file mode 100644 index 00000000..a664d011 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/emitter.py @@ -0,0 +1,1137 @@ + +# Emitter expects events obeying the following grammar: +# stream ::= STREAM-START document* STREAM-END +# document ::= DOCUMENT-START node DOCUMENT-END +# node ::= SCALAR | sequence | mapping +# sequence ::= SEQUENCE-START node* SEQUENCE-END +# mapping ::= MAPPING-START (node node)* MAPPING-END + +__all__ = ['Emitter', 'EmitterError'] + +from .error import YAMLError +from .events import * + +class EmitterError(YAMLError): + pass + +class ScalarAnalysis: + def __init__(self, scalar, empty, multiline, + allow_flow_plain, allow_block_plain, + allow_single_quoted, allow_double_quoted, + allow_block): + self.scalar = scalar + self.empty = empty + self.multiline = multiline + self.allow_flow_plain = allow_flow_plain + self.allow_block_plain = allow_block_plain + self.allow_single_quoted = allow_single_quoted + self.allow_double_quoted = allow_double_quoted + self.allow_block = allow_block + +class Emitter: + + DEFAULT_TAG_PREFIXES = { + '!' : '!', + 'tag:yaml.org,2002:' : '!!', + } + + def __init__(self, stream, canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + + # The stream should have the methods `write` and possibly `flush`. + self.stream = stream + + # Encoding can be overridden by STREAM-START. + self.encoding = None + + # Emitter is a state machine with a stack of states to handle nested + # structures. + self.states = [] + self.state = self.expect_stream_start + + # Current event and the event queue. + self.events = [] + self.event = None + + # The current indentation level and the stack of previous indents. + self.indents = [] + self.indent = None + + # Flow level. + self.flow_level = 0 + + # Contexts. + self.root_context = False + self.sequence_context = False + self.mapping_context = False + self.simple_key_context = False + + # Characteristics of the last emitted character: + # - current position. + # - is it a whitespace? + # - is it an indention character + # (indentation space, '-', '?', or ':')? + self.line = 0 + self.column = 0 + self.whitespace = True + self.indention = True + + # Whether the document requires an explicit document indicator + self.open_ended = False + + # Formatting details. + self.canonical = canonical + self.allow_unicode = allow_unicode + self.best_indent = 2 + if indent and 1 < indent < 10: + self.best_indent = indent + self.best_width = 80 + if width and width > self.best_indent*2: + self.best_width = width + self.best_line_break = '\n' + if line_break in ['\r', '\n', '\r\n']: + self.best_line_break = line_break + + # Tag prefixes. + self.tag_prefixes = None + + # Prepared anchor and tag. + self.prepared_anchor = None + self.prepared_tag = None + + # Scalar analysis and style. + self.analysis = None + self.style = None + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def emit(self, event): + self.events.append(event) + while not self.need_more_events(): + self.event = self.events.pop(0) + self.state() + self.event = None + + # In some cases, we wait for a few next events before emitting. + + def need_more_events(self): + if not self.events: + return True + event = self.events[0] + if isinstance(event, DocumentStartEvent): + return self.need_events(1) + elif isinstance(event, SequenceStartEvent): + return self.need_events(2) + elif isinstance(event, MappingStartEvent): + return self.need_events(3) + else: + return False + + def need_events(self, count): + level = 0 + for event in self.events[1:]: + if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): + level += 1 + elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): + level -= 1 + elif isinstance(event, StreamEndEvent): + level = -1 + if level < 0: + return False + return (len(self.events) < count+1) + + def increase_indent(self, flow=False, indentless=False): + self.indents.append(self.indent) + if self.indent is None: + if flow: + self.indent = self.best_indent + else: + self.indent = 0 + elif not indentless: + self.indent += self.best_indent + + # States. + + # Stream handlers. + + def expect_stream_start(self): + if isinstance(self.event, StreamStartEvent): + if self.event.encoding and not hasattr(self.stream, 'encoding'): + self.encoding = self.event.encoding + self.write_stream_start() + self.state = self.expect_first_document_start + else: + raise EmitterError("expected StreamStartEvent, but got %s" + % self.event) + + def expect_nothing(self): + raise EmitterError("expected nothing, but got %s" % self.event) + + # Document handlers. + + def expect_first_document_start(self): + return self.expect_document_start(first=True) + + def expect_document_start(self, first=False): + if isinstance(self.event, DocumentStartEvent): + if (self.event.version or self.event.tags) and self.open_ended: + self.write_indicator('...', True) + self.write_indent() + if self.event.version: + version_text = self.prepare_version(self.event.version) + self.write_version_directive(version_text) + self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() + if self.event.tags: + handles = sorted(self.event.tags.keys()) + for handle in handles: + prefix = self.event.tags[handle] + self.tag_prefixes[prefix] = handle + handle_text = self.prepare_tag_handle(handle) + prefix_text = self.prepare_tag_prefix(prefix) + self.write_tag_directive(handle_text, prefix_text) + implicit = (first and not self.event.explicit and not self.canonical + and not self.event.version and not self.event.tags + and not self.check_empty_document()) + if not implicit: + self.write_indent() + self.write_indicator('---', True) + if self.canonical: + self.write_indent() + self.state = self.expect_document_root + elif isinstance(self.event, StreamEndEvent): + if self.open_ended: + self.write_indicator('...', True) + self.write_indent() + self.write_stream_end() + self.state = self.expect_nothing + else: + raise EmitterError("expected DocumentStartEvent, but got %s" + % self.event) + + def expect_document_end(self): + if isinstance(self.event, DocumentEndEvent): + self.write_indent() + if self.event.explicit: + self.write_indicator('...', True) + self.write_indent() + self.flush_stream() + self.state = self.expect_document_start + else: + raise EmitterError("expected DocumentEndEvent, but got %s" + % self.event) + + def expect_document_root(self): + self.states.append(self.expect_document_end) + self.expect_node(root=True) + + # Node handlers. + + def expect_node(self, root=False, sequence=False, mapping=False, + simple_key=False): + self.root_context = root + self.sequence_context = sequence + self.mapping_context = mapping + self.simple_key_context = simple_key + if isinstance(self.event, AliasEvent): + self.expect_alias() + elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): + self.process_anchor('&') + self.process_tag() + if isinstance(self.event, ScalarEvent): + self.expect_scalar() + elif isinstance(self.event, SequenceStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_sequence(): + self.expect_flow_sequence() + else: + self.expect_block_sequence() + elif isinstance(self.event, MappingStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_mapping(): + self.expect_flow_mapping() + else: + self.expect_block_mapping() + else: + raise EmitterError("expected NodeEvent, but got %s" % self.event) + + def expect_alias(self): + if self.event.anchor is None: + raise EmitterError("anchor is not specified for alias") + self.process_anchor('*') + self.state = self.states.pop() + + def expect_scalar(self): + self.increase_indent(flow=True) + self.process_scalar() + self.indent = self.indents.pop() + self.state = self.states.pop() + + # Flow sequence handlers. + + def expect_flow_sequence(self): + self.write_indicator('[', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_sequence_item + + def expect_first_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator(']', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + def expect_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator(']', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + # Flow mapping handlers. + + def expect_flow_mapping(self): + self.write_indicator('{', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_mapping_key + + def expect_first_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator('}', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator('}', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + def expect_flow_mapping_value(self): + if self.canonical or self.column > self.best_width: + self.write_indent() + self.write_indicator(':', True) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + # Block sequence handlers. + + def expect_block_sequence(self): + indentless = (self.mapping_context and not self.indention) + self.increase_indent(flow=False, indentless=indentless) + self.state = self.expect_first_block_sequence_item + + def expect_first_block_sequence_item(self): + return self.expect_block_sequence_item(first=True) + + def expect_block_sequence_item(self, first=False): + if not first and isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + self.write_indicator('-', True, indention=True) + self.states.append(self.expect_block_sequence_item) + self.expect_node(sequence=True) + + # Block mapping handlers. + + def expect_block_mapping(self): + self.increase_indent(flow=False) + self.state = self.expect_first_block_mapping_key + + def expect_first_block_mapping_key(self): + return self.expect_block_mapping_key(first=True) + + def expect_block_mapping_key(self, first=False): + if not first and isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + if self.check_simple_key(): + self.states.append(self.expect_block_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True, indention=True) + self.states.append(self.expect_block_mapping_value) + self.expect_node(mapping=True) + + def expect_block_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + def expect_block_mapping_value(self): + self.write_indent() + self.write_indicator(':', True, indention=True) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + # Checkers. + + def check_empty_sequence(self): + return (isinstance(self.event, SequenceStartEvent) and self.events + and isinstance(self.events[0], SequenceEndEvent)) + + def check_empty_mapping(self): + return (isinstance(self.event, MappingStartEvent) and self.events + and isinstance(self.events[0], MappingEndEvent)) + + def check_empty_document(self): + if not isinstance(self.event, DocumentStartEvent) or not self.events: + return False + event = self.events[0] + return (isinstance(event, ScalarEvent) and event.anchor is None + and event.tag is None and event.implicit and event.value == '') + + def check_simple_key(self): + length = 0 + if isinstance(self.event, NodeEvent) and self.event.anchor is not None: + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + length += len(self.prepared_anchor) + if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ + and self.event.tag is not None: + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(self.event.tag) + length += len(self.prepared_tag) + if isinstance(self.event, ScalarEvent): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + length += len(self.analysis.scalar) + return (length < 128 and (isinstance(self.event, AliasEvent) + or (isinstance(self.event, ScalarEvent) + and not self.analysis.empty and not self.analysis.multiline) + or self.check_empty_sequence() or self.check_empty_mapping())) + + # Anchor, Tag, and Scalar processors. + + def process_anchor(self, indicator): + if self.event.anchor is None: + self.prepared_anchor = None + return + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + if self.prepared_anchor: + self.write_indicator(indicator+self.prepared_anchor, True) + self.prepared_anchor = None + + def process_tag(self): + tag = self.event.tag + if isinstance(self.event, ScalarEvent): + if self.style is None: + self.style = self.choose_scalar_style() + if ((not self.canonical or tag is None) and + ((self.style == '' and self.event.implicit[0]) + or (self.style != '' and self.event.implicit[1]))): + self.prepared_tag = None + return + if self.event.implicit[0] and tag is None: + tag = '!' + self.prepared_tag = None + else: + if (not self.canonical or tag is None) and self.event.implicit: + self.prepared_tag = None + return + if tag is None: + raise EmitterError("tag is not specified") + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(tag) + if self.prepared_tag: + self.write_indicator(self.prepared_tag, True) + self.prepared_tag = None + + def choose_scalar_style(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.event.style == '"' or self.canonical: + return '"' + if not self.event.style and self.event.implicit[0]: + if (not (self.simple_key_context and + (self.analysis.empty or self.analysis.multiline)) + and (self.flow_level and self.analysis.allow_flow_plain + or (not self.flow_level and self.analysis.allow_block_plain))): + return '' + if self.event.style and self.event.style in '|>': + if (not self.flow_level and not self.simple_key_context + and self.analysis.allow_block): + return self.event.style + if not self.event.style or self.event.style == '\'': + if (self.analysis.allow_single_quoted and + not (self.simple_key_context and self.analysis.multiline)): + return '\'' + return '"' + + def process_scalar(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.style is None: + self.style = self.choose_scalar_style() + split = (not self.simple_key_context) + #if self.analysis.multiline and split \ + # and (not self.style or self.style in '\'\"'): + # self.write_indent() + if self.style == '"': + self.write_double_quoted(self.analysis.scalar, split) + elif self.style == '\'': + self.write_single_quoted(self.analysis.scalar, split) + elif self.style == '>': + self.write_folded(self.analysis.scalar) + elif self.style == '|': + self.write_literal(self.analysis.scalar) + else: + self.write_plain(self.analysis.scalar, split) + self.analysis = None + self.style = None + + # Analyzers. + + def prepare_version(self, version): + major, minor = version + if major != 1: + raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) + return '%d.%d' % (major, minor) + + def prepare_tag_handle(self, handle): + if not handle: + raise EmitterError("tag handle must not be empty") + if handle[0] != '!' or handle[-1] != '!': + raise EmitterError("tag handle must start and end with '!': %r" % handle) + for ch in handle[1:-1]: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the tag handle: %r" + % (ch, handle)) + return handle + + def prepare_tag_prefix(self, prefix): + if not prefix: + raise EmitterError("tag prefix must not be empty") + chunks = [] + start = end = 0 + if prefix[0] == '!': + end = 1 + while end < len(prefix): + ch = prefix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?!:@&=+$,_.~*\'()[]': + end += 1 + else: + if start < end: + chunks.append(prefix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ord(ch)) + if start < end: + chunks.append(prefix[start:end]) + return ''.join(chunks) + + def prepare_tag(self, tag): + if not tag: + raise EmitterError("tag must not be empty") + if tag == '!': + return tag + handle = None + suffix = tag + prefixes = sorted(self.tag_prefixes.keys()) + for prefix in prefixes: + if tag.startswith(prefix) \ + and (prefix == '!' or len(prefix) < len(tag)): + handle = self.tag_prefixes[prefix] + suffix = tag[len(prefix):] + chunks = [] + start = end = 0 + while end < len(suffix): + ch = suffix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.~*\'()[]' \ + or (ch == '!' and handle != '!'): + end += 1 + else: + if start < end: + chunks.append(suffix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ch) + if start < end: + chunks.append(suffix[start:end]) + suffix_text = ''.join(chunks) + if handle: + return '%s%s' % (handle, suffix_text) + else: + return '!<%s>' % suffix_text + + def prepare_anchor(self, anchor): + if not anchor: + raise EmitterError("anchor must not be empty") + for ch in anchor: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the anchor: %r" + % (ch, anchor)) + return anchor + + def analyze_scalar(self, scalar): + + # Empty scalar is a special case. + if not scalar: + return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, + allow_flow_plain=False, allow_block_plain=True, + allow_single_quoted=True, allow_double_quoted=True, + allow_block=False) + + # Indicators and special characters. + block_indicators = False + flow_indicators = False + line_breaks = False + special_characters = False + + # Important whitespace combinations. + leading_space = False + leading_break = False + trailing_space = False + trailing_break = False + break_space = False + space_break = False + + # Check document indicators. + if scalar.startswith('---') or scalar.startswith('...'): + block_indicators = True + flow_indicators = True + + # First character or preceded by a whitespace. + preceded_by_whitespace = True + + # Last character or followed by a whitespace. + followed_by_whitespace = (len(scalar) == 1 or + scalar[1] in '\0 \t\r\n\x85\u2028\u2029') + + # The previous character is a space. + previous_space = False + + # The previous character is a break. + previous_break = False + + index = 0 + while index < len(scalar): + ch = scalar[index] + + # Check for indicators. + if index == 0: + # Leading indicators are special characters. + if ch in '#,[]{}&*!|>\'\"%@`': + flow_indicators = True + block_indicators = True + if ch in '?:': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '-' and followed_by_whitespace: + flow_indicators = True + block_indicators = True + else: + # Some indicators cannot appear within a scalar as well. + if ch in ',?[]{}': + flow_indicators = True + if ch == ':': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '#' and preceded_by_whitespace: + flow_indicators = True + block_indicators = True + + # Check for line breaks, special, and unicode characters. + if ch in '\n\x85\u2028\u2029': + line_breaks = True + if not (ch == '\n' or '\x20' <= ch <= '\x7E'): + if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD' + or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': + unicode_characters = True + if not self.allow_unicode: + special_characters = True + else: + special_characters = True + + # Detect important whitespace combinations. + if ch == ' ': + if index == 0: + leading_space = True + if index == len(scalar)-1: + trailing_space = True + if previous_break: + break_space = True + previous_space = True + previous_break = False + elif ch in '\n\x85\u2028\u2029': + if index == 0: + leading_break = True + if index == len(scalar)-1: + trailing_break = True + if previous_space: + space_break = True + previous_space = False + previous_break = True + else: + previous_space = False + previous_break = False + + # Prepare for the next character. + index += 1 + preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = (index+1 >= len(scalar) or + scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') + + # Let's decide what styles are allowed. + allow_flow_plain = True + allow_block_plain = True + allow_single_quoted = True + allow_double_quoted = True + allow_block = True + + # Leading and trailing whitespaces are bad for plain scalars. + if (leading_space or leading_break + or trailing_space or trailing_break): + allow_flow_plain = allow_block_plain = False + + # We do not permit trailing spaces for block scalars. + if trailing_space: + allow_block = False + + # Spaces at the beginning of a new line are only acceptable for block + # scalars. + if break_space: + allow_flow_plain = allow_block_plain = allow_single_quoted = False + + # Spaces followed by breaks, as well as special character are only + # allowed for double quoted scalars. + if space_break or special_characters: + allow_flow_plain = allow_block_plain = \ + allow_single_quoted = allow_block = False + + # Although the plain scalar writer supports breaks, we never emit + # multiline plain scalars. + if line_breaks: + allow_flow_plain = allow_block_plain = False + + # Flow indicators are forbidden for flow plain scalars. + if flow_indicators: + allow_flow_plain = False + + # Block indicators are forbidden for block plain scalars. + if block_indicators: + allow_block_plain = False + + return ScalarAnalysis(scalar=scalar, + empty=False, multiline=line_breaks, + allow_flow_plain=allow_flow_plain, + allow_block_plain=allow_block_plain, + allow_single_quoted=allow_single_quoted, + allow_double_quoted=allow_double_quoted, + allow_block=allow_block) + + # Writers. + + def flush_stream(self): + if hasattr(self.stream, 'flush'): + self.stream.flush() + + def write_stream_start(self): + # Write BOM if needed. + if self.encoding and self.encoding.startswith('utf-16'): + self.stream.write('\uFEFF'.encode(self.encoding)) + + def write_stream_end(self): + self.flush_stream() + + def write_indicator(self, indicator, need_whitespace, + whitespace=False, indention=False): + if self.whitespace or not need_whitespace: + data = indicator + else: + data = ' '+indicator + self.whitespace = whitespace + self.indention = self.indention and indention + self.column += len(data) + self.open_ended = False + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_indent(self): + indent = self.indent or 0 + if not self.indention or self.column > indent \ + or (self.column == indent and not self.whitespace): + self.write_line_break() + if self.column < indent: + self.whitespace = True + data = ' '*(indent-self.column) + self.column = indent + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_line_break(self, data=None): + if data is None: + data = self.best_line_break + self.whitespace = True + self.indention = True + self.line += 1 + self.column = 0 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_version_directive(self, version_text): + data = '%%YAML %s' % version_text + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + def write_tag_directive(self, handle_text, prefix_text): + data = '%%TAG %s %s' % (handle_text, prefix_text) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + # Scalar streams. + + def write_single_quoted(self, text, split=True): + self.write_indicator('\'', True) + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch is None or ch != ' ': + if start+1 == end and self.column > self.best_width and split \ + and start != 0 and end != len(text): + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'': + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch == '\'': + data = '\'\'' + self.column += 2 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + 1 + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + self.write_indicator('\'', False) + + ESCAPE_REPLACEMENTS = { + '\0': '0', + '\x07': 'a', + '\x08': 'b', + '\x09': 't', + '\x0A': 'n', + '\x0B': 'v', + '\x0C': 'f', + '\x0D': 'r', + '\x1B': 'e', + '\"': '\"', + '\\': '\\', + '\x85': 'N', + '\xA0': '_', + '\u2028': 'L', + '\u2029': 'P', + } + + def write_double_quoted(self, text, split=True): + self.write_indicator('"', True) + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ + or not ('\x20' <= ch <= '\x7E' + or (self.allow_unicode + and ('\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD'))): + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + if ch in self.ESCAPE_REPLACEMENTS: + data = '\\'+self.ESCAPE_REPLACEMENTS[ch] + elif ch <= '\xFF': + data = '\\x%02X' % ord(ch) + elif ch <= '\uFFFF': + data = '\\u%04X' % ord(ch) + else: + data = '\\U%08X' % ord(ch) + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end+1 + if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \ + and self.column+(end-start) > self.best_width and split: + data = text[start:end]+'\\' + if start < end: + start = end + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_indent() + self.whitespace = False + self.indention = False + if text[start] == ' ': + data = '\\' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + end += 1 + self.write_indicator('"', False) + + def determine_block_hints(self, text): + hints = '' + if text: + if text[0] in ' \n\x85\u2028\u2029': + hints += str(self.best_indent) + if text[-1] not in '\n\x85\u2028\u2029': + hints += '-' + elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029': + hints += '+' + return hints + + def write_folded(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('>'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + leading_space = True + spaces = False + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if not leading_space and ch is not None and ch != ' ' \ + and text[start] == '\n': + self.write_line_break() + leading_space = (ch == ' ') + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + elif spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width: + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + spaces = (ch == ' ') + end += 1 + + def write_literal(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('|'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + else: + if ch is None or ch in '\n\x85\u2028\u2029': + data = text[start:end] + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + + def write_plain(self, text, split=True): + if self.root_context: + self.open_ended = True + if not text: + return + if not self.whitespace: + data = ' ' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.whitespace = False + self.indention = False + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width and split: + self.write_indent() + self.whitespace = False + self.indention = False + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + self.whitespace = False + self.indention = False + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 diff --git a/testing/prajintry/pacakges/yaml/error.py b/testing/prajintry/pacakges/yaml/error.py new file mode 100644 index 00000000..b796b4dc --- /dev/null +++ b/testing/prajintry/pacakges/yaml/error.py @@ -0,0 +1,75 @@ + +__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError'] + +class Mark: + + def __init__(self, name, index, line, column, buffer, pointer): + self.name = name + self.index = index + self.line = line + self.column = column + self.buffer = buffer + self.pointer = pointer + + def get_snippet(self, indent=4, max_length=75): + if self.buffer is None: + return None + head = '' + start = self.pointer + while start > 0 and self.buffer[start-1] not in '\0\r\n\x85\u2028\u2029': + start -= 1 + if self.pointer-start > max_length/2-1: + head = ' ... ' + start += 5 + break + tail = '' + end = self.pointer + while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029': + end += 1 + if end-self.pointer > max_length/2-1: + tail = ' ... ' + end -= 5 + break + snippet = self.buffer[start:end] + return ' '*indent + head + snippet + tail + '\n' \ + + ' '*(indent+self.pointer-start+len(head)) + '^' + + def __str__(self): + snippet = self.get_snippet() + where = " in \"%s\", line %d, column %d" \ + % (self.name, self.line+1, self.column+1) + if snippet is not None: + where += ":\n"+snippet + return where + +class YAMLError(Exception): + pass + +class MarkedYAMLError(YAMLError): + + def __init__(self, context=None, context_mark=None, + problem=None, problem_mark=None, note=None): + self.context = context + self.context_mark = context_mark + self.problem = problem + self.problem_mark = problem_mark + self.note = note + + def __str__(self): + lines = [] + if self.context is not None: + lines.append(self.context) + if self.context_mark is not None \ + and (self.problem is None or self.problem_mark is None + or self.context_mark.name != self.problem_mark.name + or self.context_mark.line != self.problem_mark.line + or self.context_mark.column != self.problem_mark.column): + lines.append(str(self.context_mark)) + if self.problem is not None: + lines.append(self.problem) + if self.problem_mark is not None: + lines.append(str(self.problem_mark)) + if self.note is not None: + lines.append(self.note) + return '\n'.join(lines) + diff --git a/testing/prajintry/pacakges/yaml/events.py b/testing/prajintry/pacakges/yaml/events.py new file mode 100644 index 00000000..f79ad389 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/events.py @@ -0,0 +1,86 @@ + +# Abstract classes. + +class Event(object): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] + if hasattr(self, key)] + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +class NodeEvent(Event): + def __init__(self, anchor, start_mark=None, end_mark=None): + self.anchor = anchor + self.start_mark = start_mark + self.end_mark = end_mark + +class CollectionStartEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, + flow_style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class CollectionEndEvent(Event): + pass + +# Implementations. + +class StreamStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndEvent(Event): + pass + +class DocumentStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None, version=None, tags=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + self.version = version + self.tags = tags + +class DocumentEndEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + +class AliasEvent(NodeEvent): + pass + +class ScalarEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, value, + start_mark=None, end_mark=None, style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class SequenceStartEvent(CollectionStartEvent): + pass + +class SequenceEndEvent(CollectionEndEvent): + pass + +class MappingStartEvent(CollectionStartEvent): + pass + +class MappingEndEvent(CollectionEndEvent): + pass + diff --git a/testing/prajintry/pacakges/yaml/loader.py b/testing/prajintry/pacakges/yaml/loader.py new file mode 100644 index 00000000..e90c1122 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/loader.py @@ -0,0 +1,63 @@ + +__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader'] + +from .reader import * +from .scanner import * +from .parser import * +from .composer import * +from .constructor import * +from .resolver import * + +class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) + +# UnsafeLoader is the same as Loader (which is and was always unsafe on +# untrusted input). Use of either Loader or UnsafeLoader should be rare, since +# FullLoad should be able to load almost all YAML safely. Loader is left intact +# to ensure backwards compatibility. +class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) diff --git a/testing/prajintry/pacakges/yaml/nodes.py b/testing/prajintry/pacakges/yaml/nodes.py new file mode 100644 index 00000000..c4f070c4 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/nodes.py @@ -0,0 +1,49 @@ + +class Node(object): + def __init__(self, tag, value, start_mark, end_mark): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + value = self.value + #if isinstance(value, list): + # if len(value) == 0: + # value = '' + # elif len(value) == 1: + # value = '<1 item>' + # else: + # value = '<%d items>' % len(value) + #else: + # if len(value) > 75: + # value = repr(value[:70]+u' ... ') + # else: + # value = repr(value) + value = repr(value) + return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) + +class ScalarNode(Node): + id = 'scalar' + def __init__(self, tag, value, + start_mark=None, end_mark=None, style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class CollectionNode(Node): + def __init__(self, tag, value, + start_mark=None, end_mark=None, flow_style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class SequenceNode(CollectionNode): + id = 'sequence' + +class MappingNode(CollectionNode): + id = 'mapping' + diff --git a/testing/prajintry/pacakges/yaml/parser.py b/testing/prajintry/pacakges/yaml/parser.py new file mode 100644 index 00000000..13a5995d --- /dev/null +++ b/testing/prajintry/pacakges/yaml/parser.py @@ -0,0 +1,589 @@ + +# The following YAML grammar is LL(1) and is parsed by a recursive descent +# parser. +# +# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +# implicit_document ::= block_node DOCUMENT-END* +# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +# block_node_or_indentless_sequence ::= +# ALIAS +# | properties (block_content | indentless_block_sequence)? +# | block_content +# | indentless_block_sequence +# block_node ::= ALIAS +# | properties block_content? +# | block_content +# flow_node ::= ALIAS +# | properties flow_content? +# | flow_content +# properties ::= TAG ANCHOR? | ANCHOR TAG? +# block_content ::= block_collection | flow_collection | SCALAR +# flow_content ::= flow_collection | SCALAR +# block_collection ::= block_sequence | block_mapping +# flow_collection ::= flow_sequence | flow_mapping +# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +# indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +# block_mapping ::= BLOCK-MAPPING_START +# ((KEY block_node_or_indentless_sequence?)? +# (VALUE block_node_or_indentless_sequence?)?)* +# BLOCK-END +# flow_sequence ::= FLOW-SEQUENCE-START +# (flow_sequence_entry FLOW-ENTRY)* +# flow_sequence_entry? +# FLOW-SEQUENCE-END +# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# flow_mapping ::= FLOW-MAPPING-START +# (flow_mapping_entry FLOW-ENTRY)* +# flow_mapping_entry? +# FLOW-MAPPING-END +# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# +# FIRST sets: +# +# stream: { STREAM-START } +# explicit_document: { DIRECTIVE DOCUMENT-START } +# implicit_document: FIRST(block_node) +# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_sequence: { BLOCK-SEQUENCE-START } +# block_mapping: { BLOCK-MAPPING-START } +# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY } +# indentless_sequence: { ENTRY } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_sequence: { FLOW-SEQUENCE-START } +# flow_mapping: { FLOW-MAPPING-START } +# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } +# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } + +__all__ = ['Parser', 'ParserError'] + +from .error import MarkedYAMLError +from .tokens import * +from .events import * +from .scanner import * + +class ParserError(MarkedYAMLError): + pass + +class Parser: + # Since writing a recursive-descendant parser is a straightforward task, we + # do not give many comments here. + + DEFAULT_TAGS = { + '!': '!', + '!!': 'tag:yaml.org,2002:', + } + + def __init__(self): + self.current_event = None + self.yaml_version = None + self.tag_handles = {} + self.states = [] + self.marks = [] + self.state = self.parse_stream_start + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def check_event(self, *choices): + # Check the type of the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + if self.current_event is not None: + if not choices: + return True + for choice in choices: + if isinstance(self.current_event, choice): + return True + return False + + def peek_event(self): + # Get the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + return self.current_event + + def get_event(self): + # Get the next event and proceed further. + if self.current_event is None: + if self.state: + self.current_event = self.state() + value = self.current_event + self.current_event = None + return value + + # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + # implicit_document ::= block_node DOCUMENT-END* + # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + + def parse_stream_start(self): + + # Parse the stream start. + token = self.get_token() + event = StreamStartEvent(token.start_mark, token.end_mark, + encoding=token.encoding) + + # Prepare the next state. + self.state = self.parse_implicit_document_start + + return event + + def parse_implicit_document_start(self): + + # Parse an implicit document. + if not self.check_token(DirectiveToken, DocumentStartToken, + StreamEndToken): + self.tag_handles = self.DEFAULT_TAGS + token = self.peek_token() + start_mark = end_mark = token.start_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=False) + + # Prepare the next state. + self.states.append(self.parse_document_end) + self.state = self.parse_block_node + + return event + + else: + return self.parse_document_start() + + def parse_document_start(self): + + # Parse any extra document end indicators. + while self.check_token(DocumentEndToken): + self.get_token() + + # Parse an explicit document. + if not self.check_token(StreamEndToken): + token = self.peek_token() + start_mark = token.start_mark + version, tags = self.process_directives() + if not self.check_token(DocumentStartToken): + raise ParserError(None, None, + "expected '', but found %r" + % self.peek_token().id, + self.peek_token().start_mark) + token = self.get_token() + end_mark = token.end_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=True, version=version, tags=tags) + self.states.append(self.parse_document_end) + self.state = self.parse_document_content + else: + # Parse the end of the stream. + token = self.get_token() + event = StreamEndEvent(token.start_mark, token.end_mark) + assert not self.states + assert not self.marks + self.state = None + return event + + def parse_document_end(self): + + # Parse the document end. + token = self.peek_token() + start_mark = end_mark = token.start_mark + explicit = False + if self.check_token(DocumentEndToken): + token = self.get_token() + end_mark = token.end_mark + explicit = True + event = DocumentEndEvent(start_mark, end_mark, + explicit=explicit) + + # Prepare the next state. + self.state = self.parse_document_start + + return event + + def parse_document_content(self): + if self.check_token(DirectiveToken, + DocumentStartToken, DocumentEndToken, StreamEndToken): + event = self.process_empty_scalar(self.peek_token().start_mark) + self.state = self.states.pop() + return event + else: + return self.parse_block_node() + + def process_directives(self): + self.yaml_version = None + self.tag_handles = {} + while self.check_token(DirectiveToken): + token = self.get_token() + if token.name == 'YAML': + if self.yaml_version is not None: + raise ParserError(None, None, + "found duplicate YAML directive", token.start_mark) + major, minor = token.value + if major != 1: + raise ParserError(None, None, + "found incompatible YAML document (version 1.* is required)", + token.start_mark) + self.yaml_version = token.value + elif token.name == 'TAG': + handle, prefix = token.value + if handle in self.tag_handles: + raise ParserError(None, None, + "duplicate tag handle %r" % handle, + token.start_mark) + self.tag_handles[handle] = prefix + if self.tag_handles: + value = self.yaml_version, self.tag_handles.copy() + else: + value = self.yaml_version, None + for key in self.DEFAULT_TAGS: + if key not in self.tag_handles: + self.tag_handles[key] = self.DEFAULT_TAGS[key] + return value + + # block_node_or_indentless_sequence ::= ALIAS + # | properties (block_content | indentless_block_sequence)? + # | block_content + # | indentless_block_sequence + # block_node ::= ALIAS + # | properties block_content? + # | block_content + # flow_node ::= ALIAS + # | properties flow_content? + # | flow_content + # properties ::= TAG ANCHOR? | ANCHOR TAG? + # block_content ::= block_collection | flow_collection | SCALAR + # flow_content ::= flow_collection | SCALAR + # block_collection ::= block_sequence | block_mapping + # flow_collection ::= flow_sequence | flow_mapping + + def parse_block_node(self): + return self.parse_node(block=True) + + def parse_flow_node(self): + return self.parse_node() + + def parse_block_node_or_indentless_sequence(self): + return self.parse_node(block=True, indentless_sequence=True) + + def parse_node(self, block=False, indentless_sequence=False): + if self.check_token(AliasToken): + token = self.get_token() + event = AliasEvent(token.value, token.start_mark, token.end_mark) + self.state = self.states.pop() + else: + anchor = None + tag = None + start_mark = end_mark = tag_mark = None + if self.check_token(AnchorToken): + token = self.get_token() + start_mark = token.start_mark + end_mark = token.end_mark + anchor = token.value + if self.check_token(TagToken): + token = self.get_token() + tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + elif self.check_token(TagToken): + token = self.get_token() + start_mark = tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + if self.check_token(AnchorToken): + token = self.get_token() + end_mark = token.end_mark + anchor = token.value + if tag is not None: + handle, suffix = tag + if handle is not None: + if handle not in self.tag_handles: + raise ParserError("while parsing a node", start_mark, + "found undefined tag handle %r" % handle, + tag_mark) + tag = self.tag_handles[handle]+suffix + else: + tag = suffix + #if tag == '!': + # raise ParserError("while parsing a node", start_mark, + # "found non-specific tag '!'", tag_mark, + # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.") + if start_mark is None: + start_mark = end_mark = self.peek_token().start_mark + event = None + implicit = (tag is None or tag == '!') + if indentless_sequence and self.check_token(BlockEntryToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark) + self.state = self.parse_indentless_sequence_entry + else: + if self.check_token(ScalarToken): + token = self.get_token() + end_mark = token.end_mark + if (token.plain and tag is None) or tag == '!': + implicit = (True, False) + elif tag is None: + implicit = (False, True) + else: + implicit = (False, False) + event = ScalarEvent(anchor, tag, implicit, token.value, + start_mark, end_mark, style=token.style) + self.state = self.states.pop() + elif self.check_token(FlowSequenceStartToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_sequence_first_entry + elif self.check_token(FlowMappingStartToken): + end_mark = self.peek_token().end_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_mapping_first_key + elif block and self.check_token(BlockSequenceStartToken): + end_mark = self.peek_token().start_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_sequence_first_entry + elif block and self.check_token(BlockMappingStartToken): + end_mark = self.peek_token().start_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_mapping_first_key + elif anchor is not None or tag is not None: + # Empty scalars are allowed even if a tag or an anchor is + # specified. + event = ScalarEvent(anchor, tag, (implicit, False), '', + start_mark, end_mark) + self.state = self.states.pop() + else: + if block: + node = 'block' + else: + node = 'flow' + token = self.peek_token() + raise ParserError("while parsing a %s node" % node, start_mark, + "expected the node content, but found %r" % token.id, + token.start_mark) + return event + + # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + + def parse_block_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_sequence_entry() + + def parse_block_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, BlockEndToken): + self.states.append(self.parse_block_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_block_sequence_entry + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block collection", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + # indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + + def parse_indentless_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, + KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_indentless_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_indentless_sequence_entry + return self.process_empty_scalar(token.end_mark) + token = self.peek_token() + event = SequenceEndEvent(token.start_mark, token.start_mark) + self.state = self.states.pop() + return event + + # block_mapping ::= BLOCK-MAPPING_START + # ((KEY block_node_or_indentless_sequence?)? + # (VALUE block_node_or_indentless_sequence?)?)* + # BLOCK-END + + def parse_block_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_mapping_key() + + def parse_block_mapping_key(self): + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_value) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_value + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block mapping", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark) + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_block_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_key) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_block_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + # flow_sequence ::= FLOW-SEQUENCE-START + # (flow_sequence_entry FLOW-ENTRY)* + # flow_sequence_entry? + # FLOW-SEQUENCE-END + # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + # + # Note that while production rules for both flow_sequence_entry and + # flow_mapping_entry are equal, their interpretations are different. + # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?` + # generate an inline mapping (set syntax). + + def parse_flow_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_sequence_entry(first=True) + + def parse_flow_sequence_entry(self, first=False): + if not self.check_token(FlowSequenceEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow sequence", self.marks[-1], + "expected ',' or ']', but got %r" % token.id, token.start_mark) + + if self.check_token(KeyToken): + token = self.peek_token() + event = MappingStartEvent(None, None, True, + token.start_mark, token.end_mark, + flow_style=True) + self.state = self.parse_flow_sequence_entry_mapping_key + return event + elif not self.check_token(FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry) + return self.parse_flow_node() + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_sequence_entry_mapping_key(self): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_value + return self.process_empty_scalar(token.end_mark) + + def parse_flow_sequence_entry_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_end) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_end + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_sequence_entry_mapping_end + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_sequence_entry_mapping_end(self): + self.state = self.parse_flow_sequence_entry + token = self.peek_token() + return MappingEndEvent(token.start_mark, token.start_mark) + + # flow_mapping ::= FLOW-MAPPING-START + # (flow_mapping_entry FLOW-ENTRY)* + # flow_mapping_entry? + # FLOW-MAPPING-END + # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + + def parse_flow_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_mapping_key(first=True) + + def parse_flow_mapping_key(self, first=False): + if not self.check_token(FlowMappingEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow mapping", self.marks[-1], + "expected ',' or '}', but got %r" % token.id, token.start_mark) + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_value + return self.process_empty_scalar(token.end_mark) + elif not self.check_token(FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_empty_value) + return self.parse_flow_node() + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_key) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_mapping_empty_value(self): + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(self.peek_token().start_mark) + + def process_empty_scalar(self, mark): + return ScalarEvent(None, None, (True, False), '', mark, mark) + diff --git a/testing/prajintry/pacakges/yaml/reader.py b/testing/prajintry/pacakges/yaml/reader.py new file mode 100644 index 00000000..774b0219 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/reader.py @@ -0,0 +1,185 @@ +# This module contains abstractions for the input stream. You don't have to +# looks further, there are no pretty code. +# +# We define two classes here. +# +# Mark(source, line, column) +# It's just a record and its only use is producing nice error messages. +# Parser does not use it for any other purposes. +# +# Reader(source, data) +# Reader determines the encoding of `data` and converts it to unicode. +# Reader provides the following methods and attributes: +# reader.peek(length=1) - return the next `length` characters +# reader.forward(length=1) - move the current position to `length` characters. +# reader.index - the number of the current character. +# reader.line, stream.column - the line and the column of the current character. + +__all__ = ['Reader', 'ReaderError'] + +from .error import YAMLError, Mark + +import codecs, re + +class ReaderError(YAMLError): + + def __init__(self, name, position, character, encoding, reason): + self.name = name + self.character = character + self.position = position + self.encoding = encoding + self.reason = reason + + def __str__(self): + if isinstance(self.character, bytes): + return "'%s' codec can't decode byte #x%02x: %s\n" \ + " in \"%s\", position %d" \ + % (self.encoding, ord(self.character), self.reason, + self.name, self.position) + else: + return "unacceptable character #x%04x: %s\n" \ + " in \"%s\", position %d" \ + % (self.character, self.reason, + self.name, self.position) + +class Reader(object): + # Reader: + # - determines the data encoding and converts it to a unicode string, + # - checks if characters are in allowed range, + # - adds '\0' to the end. + + # Reader accepts + # - a `bytes` object, + # - a `str` object, + # - a file-like object with its `read` method returning `str`, + # - a file-like object with its `read` method returning `unicode`. + + # Yeah, it's ugly and slow. + + def __init__(self, stream): + self.name = None + self.stream = None + self.stream_pointer = 0 + self.eof = True + self.buffer = '' + self.pointer = 0 + self.raw_buffer = None + self.raw_decode = None + self.encoding = None + self.index = 0 + self.line = 0 + self.column = 0 + if isinstance(stream, str): + self.name = "" + self.check_printable(stream) + self.buffer = stream+'\0' + elif isinstance(stream, bytes): + self.name = "" + self.raw_buffer = stream + self.determine_encoding() + else: + self.stream = stream + self.name = getattr(stream, 'name', "") + self.eof = False + self.raw_buffer = None + self.determine_encoding() + + def peek(self, index=0): + try: + return self.buffer[self.pointer+index] + except IndexError: + self.update(index+1) + return self.buffer[self.pointer+index] + + def prefix(self, length=1): + if self.pointer+length >= len(self.buffer): + self.update(length) + return self.buffer[self.pointer:self.pointer+length] + + def forward(self, length=1): + if self.pointer+length+1 >= len(self.buffer): + self.update(length+1) + while length: + ch = self.buffer[self.pointer] + self.pointer += 1 + self.index += 1 + if ch in '\n\x85\u2028\u2029' \ + or (ch == '\r' and self.buffer[self.pointer] != '\n'): + self.line += 1 + self.column = 0 + elif ch != '\uFEFF': + self.column += 1 + length -= 1 + + def get_mark(self): + if self.stream is None: + return Mark(self.name, self.index, self.line, self.column, + self.buffer, self.pointer) + else: + return Mark(self.name, self.index, self.line, self.column, + None, None) + + def determine_encoding(self): + while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): + self.update_raw() + if isinstance(self.raw_buffer, bytes): + if self.raw_buffer.startswith(codecs.BOM_UTF16_LE): + self.raw_decode = codecs.utf_16_le_decode + self.encoding = 'utf-16-le' + elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE): + self.raw_decode = codecs.utf_16_be_decode + self.encoding = 'utf-16-be' + else: + self.raw_decode = codecs.utf_8_decode + self.encoding = 'utf-8' + self.update(1) + + NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]') + def check_printable(self, data): + match = self.NON_PRINTABLE.search(data) + if match: + character = match.group() + position = self.index+(len(self.buffer)-self.pointer)+match.start() + raise ReaderError(self.name, position, ord(character), + 'unicode', "special characters are not allowed") + + def update(self, length): + if self.raw_buffer is None: + return + self.buffer = self.buffer[self.pointer:] + self.pointer = 0 + while len(self.buffer) < length: + if not self.eof: + self.update_raw() + if self.raw_decode is not None: + try: + data, converted = self.raw_decode(self.raw_buffer, + 'strict', self.eof) + except UnicodeDecodeError as exc: + character = self.raw_buffer[exc.start] + if self.stream is not None: + position = self.stream_pointer-len(self.raw_buffer)+exc.start + else: + position = exc.start + raise ReaderError(self.name, position, character, + exc.encoding, exc.reason) + else: + data = self.raw_buffer + converted = len(data) + self.check_printable(data) + self.buffer += data + self.raw_buffer = self.raw_buffer[converted:] + if self.eof: + self.buffer += '\0' + self.raw_buffer = None + break + + def update_raw(self, size=4096): + data = self.stream.read(size) + if self.raw_buffer is None: + self.raw_buffer = data + else: + self.raw_buffer += data + self.stream_pointer += len(data) + if not data: + self.eof = True diff --git a/testing/prajintry/pacakges/yaml/representer.py b/testing/prajintry/pacakges/yaml/representer.py new file mode 100644 index 00000000..808ca06d --- /dev/null +++ b/testing/prajintry/pacakges/yaml/representer.py @@ -0,0 +1,389 @@ + +__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', + 'RepresenterError'] + +from .error import * +from .nodes import * + +import datetime, copyreg, types, base64, collections + +class RepresenterError(YAMLError): + pass + +class BaseRepresenter: + + yaml_representers = {} + yaml_multi_representers = {} + + def __init__(self, default_style=None, default_flow_style=False, sort_keys=True): + self.default_style = default_style + self.sort_keys = sort_keys + self.default_flow_style = default_flow_style + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent(self, data): + node = self.represent_data(data) + self.serialize(node) + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent_data(self, data): + if self.ignore_aliases(data): + self.alias_key = None + else: + self.alias_key = id(data) + if self.alias_key is not None: + if self.alias_key in self.represented_objects: + node = self.represented_objects[self.alias_key] + #if node is None: + # raise RepresenterError("recursive objects are not allowed: %r" % data) + return node + #self.represented_objects[alias_key] = None + self.object_keeper.append(data) + data_types = type(data).__mro__ + if data_types[0] in self.yaml_representers: + node = self.yaml_representers[data_types[0]](self, data) + else: + for data_type in data_types: + if data_type in self.yaml_multi_representers: + node = self.yaml_multi_representers[data_type](self, data) + break + else: + if None in self.yaml_multi_representers: + node = self.yaml_multi_representers[None](self, data) + elif None in self.yaml_representers: + node = self.yaml_representers[None](self, data) + else: + node = ScalarNode(None, str(data)) + #if alias_key is not None: + # self.represented_objects[alias_key] = node + return node + + @classmethod + def add_representer(cls, data_type, representer): + if not 'yaml_representers' in cls.__dict__: + cls.yaml_representers = cls.yaml_representers.copy() + cls.yaml_representers[data_type] = representer + + @classmethod + def add_multi_representer(cls, data_type, representer): + if not 'yaml_multi_representers' in cls.__dict__: + cls.yaml_multi_representers = cls.yaml_multi_representers.copy() + cls.yaml_multi_representers[data_type] = representer + + def represent_scalar(self, tag, value, style=None): + if style is None: + style = self.default_style + node = ScalarNode(tag, value, style=style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + return node + + def represent_sequence(self, tag, sequence, flow_style=None): + value = [] + node = SequenceNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + for item in sequence: + node_item = self.represent_data(item) + if not (isinstance(node_item, ScalarNode) and not node_item.style): + best_style = False + value.append(node_item) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def represent_mapping(self, tag, mapping, flow_style=None): + value = [] + node = MappingNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + if hasattr(mapping, 'items'): + mapping = list(mapping.items()) + if self.sort_keys: + try: + mapping = sorted(mapping) + except TypeError: + pass + for item_key, item_value in mapping: + node_key = self.represent_data(item_key) + node_value = self.represent_data(item_value) + if not (isinstance(node_key, ScalarNode) and not node_key.style): + best_style = False + if not (isinstance(node_value, ScalarNode) and not node_value.style): + best_style = False + value.append((node_key, node_value)) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def ignore_aliases(self, data): + return False + +class SafeRepresenter(BaseRepresenter): + + def ignore_aliases(self, data): + if data is None: + return True + if isinstance(data, tuple) and data == (): + return True + if isinstance(data, (str, bytes, bool, int, float)): + return True + + def represent_none(self, data): + return self.represent_scalar('tag:yaml.org,2002:null', 'null') + + def represent_str(self, data): + return self.represent_scalar('tag:yaml.org,2002:str', data) + + def represent_binary(self, data): + if hasattr(base64, 'encodebytes'): + data = base64.encodebytes(data).decode('ascii') + else: + data = base64.encodestring(data).decode('ascii') + return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') + + def represent_bool(self, data): + if data: + value = 'true' + else: + value = 'false' + return self.represent_scalar('tag:yaml.org,2002:bool', value) + + def represent_int(self, data): + return self.represent_scalar('tag:yaml.org,2002:int', str(data)) + + inf_value = 1e300 + while repr(inf_value) != repr(inf_value*inf_value): + inf_value *= inf_value + + def represent_float(self, data): + if data != data or (data == 0.0 and data == 1.0): + value = '.nan' + elif data == self.inf_value: + value = '.inf' + elif data == -self.inf_value: + value = '-.inf' + else: + value = repr(data).lower() + # Note that in some cases `repr(data)` represents a float number + # without the decimal parts. For instance: + # >>> repr(1e17) + # '1e17' + # Unfortunately, this is not a valid float representation according + # to the definition of the `!!float` tag. We fix this by adding + # '.0' before the 'e' symbol. + if '.' not in value and 'e' in value: + value = value.replace('e', '.0e', 1) + return self.represent_scalar('tag:yaml.org,2002:float', value) + + def represent_list(self, data): + #pairs = (len(data) > 0 and isinstance(data, list)) + #if pairs: + # for item in data: + # if not isinstance(item, tuple) or len(item) != 2: + # pairs = False + # break + #if not pairs: + return self.represent_sequence('tag:yaml.org,2002:seq', data) + #value = [] + #for item_key, item_value in data: + # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', + # [(item_key, item_value)])) + #return SequenceNode(u'tag:yaml.org,2002:pairs', value) + + def represent_dict(self, data): + return self.represent_mapping('tag:yaml.org,2002:map', data) + + def represent_set(self, data): + value = {} + for key in data: + value[key] = None + return self.represent_mapping('tag:yaml.org,2002:set', value) + + def represent_date(self, data): + value = data.isoformat() + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_datetime(self, data): + value = data.isoformat(' ') + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) + + def represent_undefined(self, data): + raise RepresenterError("cannot represent an object", data) + +SafeRepresenter.add_representer(type(None), + SafeRepresenter.represent_none) + +SafeRepresenter.add_representer(str, + SafeRepresenter.represent_str) + +SafeRepresenter.add_representer(bytes, + SafeRepresenter.represent_binary) + +SafeRepresenter.add_representer(bool, + SafeRepresenter.represent_bool) + +SafeRepresenter.add_representer(int, + SafeRepresenter.represent_int) + +SafeRepresenter.add_representer(float, + SafeRepresenter.represent_float) + +SafeRepresenter.add_representer(list, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(tuple, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(dict, + SafeRepresenter.represent_dict) + +SafeRepresenter.add_representer(set, + SafeRepresenter.represent_set) + +SafeRepresenter.add_representer(datetime.date, + SafeRepresenter.represent_date) + +SafeRepresenter.add_representer(datetime.datetime, + SafeRepresenter.represent_datetime) + +SafeRepresenter.add_representer(None, + SafeRepresenter.represent_undefined) + +class Representer(SafeRepresenter): + + def represent_complex(self, data): + if data.imag == 0.0: + data = '%r' % data.real + elif data.real == 0.0: + data = '%rj' % data.imag + elif data.imag > 0: + data = '%r+%rj' % (data.real, data.imag) + else: + data = '%r%rj' % (data.real, data.imag) + return self.represent_scalar('tag:yaml.org,2002:python/complex', data) + + def represent_tuple(self, data): + return self.represent_sequence('tag:yaml.org,2002:python/tuple', data) + + def represent_name(self, data): + name = '%s.%s' % (data.__module__, data.__name__) + return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '') + + def represent_module(self, data): + return self.represent_scalar( + 'tag:yaml.org,2002:python/module:'+data.__name__, '') + + def represent_object(self, data): + # We use __reduce__ API to save the data. data.__reduce__ returns + # a tuple of length 2-5: + # (function, args, state, listitems, dictitems) + + # For reconstructing, we calls function(*args), then set its state, + # listitems, and dictitems if they are not None. + + # A special case is when function.__name__ == '__newobj__'. In this + # case we create the object with args[0].__new__(*args). + + # Another special case is when __reduce__ returns a string - we don't + # support it. + + # We produce a !!python/object, !!python/object/new or + # !!python/object/apply node. + + cls = type(data) + if cls in copyreg.dispatch_table: + reduce = copyreg.dispatch_table[cls](data) + elif hasattr(data, '__reduce_ex__'): + reduce = data.__reduce_ex__(2) + elif hasattr(data, '__reduce__'): + reduce = data.__reduce__() + else: + raise RepresenterError("cannot represent an object", data) + reduce = (list(reduce)+[None]*5)[:5] + function, args, state, listitems, dictitems = reduce + args = list(args) + if state is None: + state = {} + if listitems is not None: + listitems = list(listitems) + if dictitems is not None: + dictitems = dict(dictitems) + if function.__name__ == '__newobj__': + function = args[0] + args = args[1:] + tag = 'tag:yaml.org,2002:python/object/new:' + newobj = True + else: + tag = 'tag:yaml.org,2002:python/object/apply:' + newobj = False + function_name = '%s.%s' % (function.__module__, function.__name__) + if not args and not listitems and not dictitems \ + and isinstance(state, dict) and newobj: + return self.represent_mapping( + 'tag:yaml.org,2002:python/object:'+function_name, state) + if not listitems and not dictitems \ + and isinstance(state, dict) and not state: + return self.represent_sequence(tag+function_name, args) + value = {} + if args: + value['args'] = args + if state or not isinstance(state, dict): + value['state'] = state + if listitems: + value['listitems'] = listitems + if dictitems: + value['dictitems'] = dictitems + return self.represent_mapping(tag+function_name, value) + + def represent_ordered_dict(self, data): + # Provide uniform representation across different Python versions. + data_type = type(data) + tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \ + % (data_type.__module__, data_type.__name__) + items = [[key, value] for key, value in data.items()] + return self.represent_sequence(tag, [items]) + +Representer.add_representer(complex, + Representer.represent_complex) + +Representer.add_representer(tuple, + Representer.represent_tuple) + +Representer.add_multi_representer(type, + Representer.represent_name) + +Representer.add_representer(collections.OrderedDict, + Representer.represent_ordered_dict) + +Representer.add_representer(types.FunctionType, + Representer.represent_name) + +Representer.add_representer(types.BuiltinFunctionType, + Representer.represent_name) + +Representer.add_representer(types.ModuleType, + Representer.represent_module) + +Representer.add_multi_representer(object, + Representer.represent_object) + diff --git a/testing/prajintry/pacakges/yaml/resolver.py b/testing/prajintry/pacakges/yaml/resolver.py new file mode 100644 index 00000000..3522bdaa --- /dev/null +++ b/testing/prajintry/pacakges/yaml/resolver.py @@ -0,0 +1,227 @@ + +__all__ = ['BaseResolver', 'Resolver'] + +from .error import * +from .nodes import * + +import re + +class ResolverError(YAMLError): + pass + +class BaseResolver: + + DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str' + DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq' + DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' + + yaml_implicit_resolvers = {} + yaml_path_resolvers = {} + + def __init__(self): + self.resolver_exact_paths = [] + self.resolver_prefix_paths = [] + + @classmethod + def add_implicit_resolver(cls, tag, regexp, first): + if not 'yaml_implicit_resolvers' in cls.__dict__: + implicit_resolvers = {} + for key in cls.yaml_implicit_resolvers: + implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:] + cls.yaml_implicit_resolvers = implicit_resolvers + if first is None: + first = [None] + for ch in first: + cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) + + @classmethod + def add_path_resolver(cls, tag, path, kind=None): + # Note: `add_path_resolver` is experimental. The API could be changed. + # `new_path` is a pattern that is matched against the path from the + # root to the node that is being considered. `node_path` elements are + # tuples `(node_check, index_check)`. `node_check` is a node class: + # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None` + # matches any kind of a node. `index_check` could be `None`, a boolean + # value, a string value, or a number. `None` and `False` match against + # any _value_ of sequence and mapping nodes. `True` matches against + # any _key_ of a mapping node. A string `index_check` matches against + # a mapping value that corresponds to a scalar key which content is + # equal to the `index_check` value. An integer `index_check` matches + # against a sequence value with the index equal to `index_check`. + if not 'yaml_path_resolvers' in cls.__dict__: + cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy() + new_path = [] + for element in path: + if isinstance(element, (list, tuple)): + if len(element) == 2: + node_check, index_check = element + elif len(element) == 1: + node_check = element[0] + index_check = True + else: + raise ResolverError("Invalid path element: %s" % element) + else: + node_check = None + index_check = element + if node_check is str: + node_check = ScalarNode + elif node_check is list: + node_check = SequenceNode + elif node_check is dict: + node_check = MappingNode + elif node_check not in [ScalarNode, SequenceNode, MappingNode] \ + and not isinstance(node_check, str) \ + and node_check is not None: + raise ResolverError("Invalid node checker: %s" % node_check) + if not isinstance(index_check, (str, int)) \ + and index_check is not None: + raise ResolverError("Invalid index checker: %s" % index_check) + new_path.append((node_check, index_check)) + if kind is str: + kind = ScalarNode + elif kind is list: + kind = SequenceNode + elif kind is dict: + kind = MappingNode + elif kind not in [ScalarNode, SequenceNode, MappingNode] \ + and kind is not None: + raise ResolverError("Invalid node kind: %s" % kind) + cls.yaml_path_resolvers[tuple(new_path), kind] = tag + + def descend_resolver(self, current_node, current_index): + if not self.yaml_path_resolvers: + return + exact_paths = {} + prefix_paths = [] + if current_node: + depth = len(self.resolver_prefix_paths) + for path, kind in self.resolver_prefix_paths[-1]: + if self.check_resolver_prefix(depth, path, kind, + current_node, current_index): + if len(path) > depth: + prefix_paths.append((path, kind)) + else: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + for path, kind in self.yaml_path_resolvers: + if not path: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + prefix_paths.append((path, kind)) + self.resolver_exact_paths.append(exact_paths) + self.resolver_prefix_paths.append(prefix_paths) + + def ascend_resolver(self): + if not self.yaml_path_resolvers: + return + self.resolver_exact_paths.pop() + self.resolver_prefix_paths.pop() + + def check_resolver_prefix(self, depth, path, kind, + current_node, current_index): + node_check, index_check = path[depth-1] + if isinstance(node_check, str): + if current_node.tag != node_check: + return + elif node_check is not None: + if not isinstance(current_node, node_check): + return + if index_check is True and current_index is not None: + return + if (index_check is False or index_check is None) \ + and current_index is None: + return + if isinstance(index_check, str): + if not (isinstance(current_index, ScalarNode) + and index_check == current_index.value): + return + elif isinstance(index_check, int) and not isinstance(index_check, bool): + if index_check != current_index: + return + return True + + def resolve(self, kind, value, implicit): + if kind is ScalarNode and implicit[0]: + if value == '': + resolvers = self.yaml_implicit_resolvers.get('', []) + else: + resolvers = self.yaml_implicit_resolvers.get(value[0], []) + wildcard_resolvers = self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers + wildcard_resolvers: + if regexp.match(value): + return tag + implicit = implicit[1] + if self.yaml_path_resolvers: + exact_paths = self.resolver_exact_paths[-1] + if kind in exact_paths: + return exact_paths[kind] + if None in exact_paths: + return exact_paths[None] + if kind is ScalarNode: + return self.DEFAULT_SCALAR_TAG + elif kind is SequenceNode: + return self.DEFAULT_SEQUENCE_TAG + elif kind is MappingNode: + return self.DEFAULT_MAPPING_TAG + +class Resolver(BaseResolver): + pass + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:bool', + re.compile(r'''^(?:yes|Yes|YES|no|No|NO + |true|True|TRUE|false|False|FALSE + |on|On|ON|off|Off|OFF)$''', re.X), + list('yYnNtTfFoO')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:float', + re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + |\.[0-9][0-9_]*(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN))$''', re.X), + list('-+0123456789.')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:int', + re.compile(r'''^(?:[-+]?0b[0-1_]+ + |[-+]?0[0-7_]+ + |[-+]?(?:0|[1-9][0-9_]*) + |[-+]?0x[0-9a-fA-F_]+ + |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), + list('-+0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:merge', + re.compile(r'^(?:<<)$'), + ['<']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:null', + re.compile(r'''^(?: ~ + |null|Null|NULL + | )$''', re.X), + ['~', 'n', 'N', '']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:timestamp', + re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? + (?:[Tt]|[ \t]+)[0-9][0-9]? + :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? + (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), + list('0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:value', + re.compile(r'^(?:=)$'), + ['=']) + +# The following resolver is only for documentation purposes. It cannot work +# because plain scalars cannot start with '!', '&', or '*'. +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:yaml', + re.compile(r'^(?:!|&|\*)$'), + list('!&*')) + diff --git a/testing/prajintry/pacakges/yaml/scanner.py b/testing/prajintry/pacakges/yaml/scanner.py new file mode 100644 index 00000000..de925b07 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/scanner.py @@ -0,0 +1,1435 @@ + +# Scanner produces tokens of the following types: +# STREAM-START +# STREAM-END +# DIRECTIVE(name, value) +# DOCUMENT-START +# DOCUMENT-END +# BLOCK-SEQUENCE-START +# BLOCK-MAPPING-START +# BLOCK-END +# FLOW-SEQUENCE-START +# FLOW-MAPPING-START +# FLOW-SEQUENCE-END +# FLOW-MAPPING-END +# BLOCK-ENTRY +# FLOW-ENTRY +# KEY +# VALUE +# ALIAS(value) +# ANCHOR(value) +# TAG(value) +# SCALAR(value, plain, style) +# +# Read comments in the Scanner code for more details. +# + +__all__ = ['Scanner', 'ScannerError'] + +from .error import MarkedYAMLError +from .tokens import * + +class ScannerError(MarkedYAMLError): + pass + +class SimpleKey: + # See below simple keys treatment. + + def __init__(self, token_number, required, index, line, column, mark): + self.token_number = token_number + self.required = required + self.index = index + self.line = line + self.column = column + self.mark = mark + +class Scanner: + + def __init__(self): + """Initialize the scanner.""" + # It is assumed that Scanner and Reader will have a common descendant. + # Reader do the dirty work of checking for BOM and converting the + # input data to Unicode. It also adds NUL to the end. + # + # Reader supports the following methods + # self.peek(i=0) # peek the next i-th character + # self.prefix(l=1) # peek the next l characters + # self.forward(l=1) # read the next l characters and move the pointer. + + # Had we reached the end of the stream? + self.done = False + + # The number of unclosed '{' and '['. `flow_level == 0` means block + # context. + self.flow_level = 0 + + # List of processed tokens that are not yet emitted. + self.tokens = [] + + # Add the STREAM-START token. + self.fetch_stream_start() + + # Number of tokens that were emitted through the `get_token` method. + self.tokens_taken = 0 + + # The current indentation level. + self.indent = -1 + + # Past indentation levels. + self.indents = [] + + # Variables related to simple keys treatment. + + # A simple key is a key that is not denoted by the '?' indicator. + # Example of simple keys: + # --- + # block simple key: value + # ? not a simple key: + # : { flow simple key: value } + # We emit the KEY token before all keys, so when we find a potential + # simple key, we try to locate the corresponding ':' indicator. + # Simple keys should be limited to a single line and 1024 characters. + + # Can a simple key start at the current position? A simple key may + # start: + # - at the beginning of the line, not counting indentation spaces + # (in block context), + # - after '{', '[', ',' (in the flow context), + # - after '?', ':', '-' (in the block context). + # In the block context, this flag also signifies if a block collection + # may start at the current position. + self.allow_simple_key = True + + # Keep track of possible simple keys. This is a dictionary. The key + # is `flow_level`; there can be no more that one possible simple key + # for each level. The value is a SimpleKey record: + # (token_number, required, index, line, column, mark) + # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow), + # '[', or '{' tokens. + self.possible_simple_keys = {} + + # Public methods. + + def check_token(self, *choices): + # Check if the next token is one of the given types. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + if not choices: + return True + for choice in choices: + if isinstance(self.tokens[0], choice): + return True + return False + + def peek_token(self): + # Return the next token, but do not delete if from the queue. + # Return None if no more tokens. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + return self.tokens[0] + else: + return None + + def get_token(self): + # Return the next token. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + self.tokens_taken += 1 + return self.tokens.pop(0) + + # Private methods. + + def need_more_tokens(self): + if self.done: + return False + if not self.tokens: + return True + # The current token may be a potential simple key, so we + # need to look further. + self.stale_possible_simple_keys() + if self.next_possible_simple_key() == self.tokens_taken: + return True + + def fetch_more_tokens(self): + + # Eat whitespaces and comments until we reach the next token. + self.scan_to_next_token() + + # Remove obsolete possible simple keys. + self.stale_possible_simple_keys() + + # Compare the current indentation and column. It may add some tokens + # and decrease the current indentation level. + self.unwind_indent(self.column) + + # Peek the next character. + ch = self.peek() + + # Is it the end of stream? + if ch == '\0': + return self.fetch_stream_end() + + # Is it a directive? + if ch == '%' and self.check_directive(): + return self.fetch_directive() + + # Is it the document start? + if ch == '-' and self.check_document_start(): + return self.fetch_document_start() + + # Is it the document end? + if ch == '.' and self.check_document_end(): + return self.fetch_document_end() + + # TODO: support for BOM within a stream. + #if ch == '\uFEFF': + # return self.fetch_bom() <-- issue BOMToken + + # Note: the order of the following checks is NOT significant. + + # Is it the flow sequence start indicator? + if ch == '[': + return self.fetch_flow_sequence_start() + + # Is it the flow mapping start indicator? + if ch == '{': + return self.fetch_flow_mapping_start() + + # Is it the flow sequence end indicator? + if ch == ']': + return self.fetch_flow_sequence_end() + + # Is it the flow mapping end indicator? + if ch == '}': + return self.fetch_flow_mapping_end() + + # Is it the flow entry indicator? + if ch == ',': + return self.fetch_flow_entry() + + # Is it the block entry indicator? + if ch == '-' and self.check_block_entry(): + return self.fetch_block_entry() + + # Is it the key indicator? + if ch == '?' and self.check_key(): + return self.fetch_key() + + # Is it the value indicator? + if ch == ':' and self.check_value(): + return self.fetch_value() + + # Is it an alias? + if ch == '*': + return self.fetch_alias() + + # Is it an anchor? + if ch == '&': + return self.fetch_anchor() + + # Is it a tag? + if ch == '!': + return self.fetch_tag() + + # Is it a literal scalar? + if ch == '|' and not self.flow_level: + return self.fetch_literal() + + # Is it a folded scalar? + if ch == '>' and not self.flow_level: + return self.fetch_folded() + + # Is it a single quoted scalar? + if ch == '\'': + return self.fetch_single() + + # Is it a double quoted scalar? + if ch == '\"': + return self.fetch_double() + + # It must be a plain scalar then. + if self.check_plain(): + return self.fetch_plain() + + # No? It's an error. Let's produce a nice error message. + raise ScannerError("while scanning for the next token", None, + "found character %r that cannot start any token" % ch, + self.get_mark()) + + # Simple keys treatment. + + def next_possible_simple_key(self): + # Return the number of the nearest possible simple key. Actually we + # don't need to loop through the whole dictionary. We may replace it + # with the following code: + # if not self.possible_simple_keys: + # return None + # return self.possible_simple_keys[ + # min(self.possible_simple_keys.keys())].token_number + min_token_number = None + for level in self.possible_simple_keys: + key = self.possible_simple_keys[level] + if min_token_number is None or key.token_number < min_token_number: + min_token_number = key.token_number + return min_token_number + + def stale_possible_simple_keys(self): + # Remove entries that are no longer possible simple keys. According to + # the YAML specification, simple keys + # - should be limited to a single line, + # - should be no longer than 1024 characters. + # Disabling this procedure will allow simple keys of any length and + # height (may cause problems if indentation is broken though). + for level in list(self.possible_simple_keys): + key = self.possible_simple_keys[level] + if key.line != self.line \ + or self.index-key.index > 1024: + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + del self.possible_simple_keys[level] + + def save_possible_simple_key(self): + # The next token may start a simple key. We check if it's possible + # and save its position. This function is called for + # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'. + + # Check if a simple key is required at the current position. + required = not self.flow_level and self.indent == self.column + + # The next token might be a simple key. Let's save it's number and + # position. + if self.allow_simple_key: + self.remove_possible_simple_key() + token_number = self.tokens_taken+len(self.tokens) + key = SimpleKey(token_number, required, + self.index, self.line, self.column, self.get_mark()) + self.possible_simple_keys[self.flow_level] = key + + def remove_possible_simple_key(self): + # Remove the saved possible key position at the current flow level. + if self.flow_level in self.possible_simple_keys: + key = self.possible_simple_keys[self.flow_level] + + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + + del self.possible_simple_keys[self.flow_level] + + # Indentation functions. + + def unwind_indent(self, column): + + ## In flow context, tokens should respect indentation. + ## Actually the condition should be `self.indent >= column` according to + ## the spec. But this condition will prohibit intuitively correct + ## constructions such as + ## key : { + ## } + #if self.flow_level and self.indent > column: + # raise ScannerError(None, None, + # "invalid indentation or unclosed '[' or '{'", + # self.get_mark()) + + # In the flow context, indentation is ignored. We make the scanner less + # restrictive then specification requires. + if self.flow_level: + return + + # In block context, we may need to issue the BLOCK-END tokens. + while self.indent > column: + mark = self.get_mark() + self.indent = self.indents.pop() + self.tokens.append(BlockEndToken(mark, mark)) + + def add_indent(self, column): + # Check if we need to increase indentation. + if self.indent < column: + self.indents.append(self.indent) + self.indent = column + return True + return False + + # Fetchers. + + def fetch_stream_start(self): + # We always add STREAM-START as the first token and STREAM-END as the + # last token. + + # Read the token. + mark = self.get_mark() + + # Add STREAM-START. + self.tokens.append(StreamStartToken(mark, mark, + encoding=self.encoding)) + + + def fetch_stream_end(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + self.possible_simple_keys = {} + + # Read the token. + mark = self.get_mark() + + # Add STREAM-END. + self.tokens.append(StreamEndToken(mark, mark)) + + # The steam is finished. + self.done = True + + def fetch_directive(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Scan and add DIRECTIVE. + self.tokens.append(self.scan_directive()) + + def fetch_document_start(self): + self.fetch_document_indicator(DocumentStartToken) + + def fetch_document_end(self): + self.fetch_document_indicator(DocumentEndToken) + + def fetch_document_indicator(self, TokenClass): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. Note that there could not be a block collection + # after '---'. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Add DOCUMENT-START or DOCUMENT-END. + start_mark = self.get_mark() + self.forward(3) + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_start(self): + self.fetch_flow_collection_start(FlowSequenceStartToken) + + def fetch_flow_mapping_start(self): + self.fetch_flow_collection_start(FlowMappingStartToken) + + def fetch_flow_collection_start(self, TokenClass): + + # '[' and '{' may start a simple key. + self.save_possible_simple_key() + + # Increase the flow level. + self.flow_level += 1 + + # Simple keys are allowed after '[' and '{'. + self.allow_simple_key = True + + # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_end(self): + self.fetch_flow_collection_end(FlowSequenceEndToken) + + def fetch_flow_mapping_end(self): + self.fetch_flow_collection_end(FlowMappingEndToken) + + def fetch_flow_collection_end(self, TokenClass): + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Decrease the flow level. + self.flow_level -= 1 + + # No simple keys after ']' or '}'. + self.allow_simple_key = False + + # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_entry(self): + + # Simple keys are allowed after ','. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add FLOW-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(FlowEntryToken(start_mark, end_mark)) + + def fetch_block_entry(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a new entry? + if not self.allow_simple_key: + raise ScannerError(None, None, + "sequence entries are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-SEQUENCE-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockSequenceStartToken(mark, mark)) + + # It's an error for the block entry to occur in the flow context, + # but we let the parser detect this. + else: + pass + + # Simple keys are allowed after '-'. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add BLOCK-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(BlockEntryToken(start_mark, end_mark)) + + def fetch_key(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a key (not necessary a simple)? + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping keys are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-MAPPING-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after '?' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add KEY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(KeyToken(start_mark, end_mark)) + + def fetch_value(self): + + # Do we determine a simple key? + if self.flow_level in self.possible_simple_keys: + + # Add KEY. + key = self.possible_simple_keys[self.flow_level] + del self.possible_simple_keys[self.flow_level] + self.tokens.insert(key.token_number-self.tokens_taken, + KeyToken(key.mark, key.mark)) + + # If this key starts a new block mapping, we need to add + # BLOCK-MAPPING-START. + if not self.flow_level: + if self.add_indent(key.column): + self.tokens.insert(key.token_number-self.tokens_taken, + BlockMappingStartToken(key.mark, key.mark)) + + # There cannot be two simple keys one after another. + self.allow_simple_key = False + + # It must be a part of a complex key. + else: + + # Block context needs additional checks. + # (Do we really need them? They will be caught by the parser + # anyway.) + if not self.flow_level: + + # We are allowed to start a complex value if and only if + # we can start a simple key. + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping values are not allowed here", + self.get_mark()) + + # If this value starts a new block mapping, we need to add + # BLOCK-MAPPING-START. It will be detected as an error later by + # the parser. + if not self.flow_level: + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after ':' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add VALUE. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(ValueToken(start_mark, end_mark)) + + def fetch_alias(self): + + # ALIAS could be a simple key. + self.save_possible_simple_key() + + # No simple keys after ALIAS. + self.allow_simple_key = False + + # Scan and add ALIAS. + self.tokens.append(self.scan_anchor(AliasToken)) + + def fetch_anchor(self): + + # ANCHOR could start a simple key. + self.save_possible_simple_key() + + # No simple keys after ANCHOR. + self.allow_simple_key = False + + # Scan and add ANCHOR. + self.tokens.append(self.scan_anchor(AnchorToken)) + + def fetch_tag(self): + + # TAG could start a simple key. + self.save_possible_simple_key() + + # No simple keys after TAG. + self.allow_simple_key = False + + # Scan and add TAG. + self.tokens.append(self.scan_tag()) + + def fetch_literal(self): + self.fetch_block_scalar(style='|') + + def fetch_folded(self): + self.fetch_block_scalar(style='>') + + def fetch_block_scalar(self, style): + + # A simple key may follow a block scalar. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Scan and add SCALAR. + self.tokens.append(self.scan_block_scalar(style)) + + def fetch_single(self): + self.fetch_flow_scalar(style='\'') + + def fetch_double(self): + self.fetch_flow_scalar(style='"') + + def fetch_flow_scalar(self, style): + + # A flow scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after flow scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. + self.tokens.append(self.scan_flow_scalar(style)) + + def fetch_plain(self): + + # A plain scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after plain scalars. But note that `scan_plain` will + # change this flag if the scan is finished at the beginning of the + # line. + self.allow_simple_key = False + + # Scan and add SCALAR. May change `allow_simple_key`. + self.tokens.append(self.scan_plain()) + + # Checkers. + + def check_directive(self): + + # DIRECTIVE: ^ '%' ... + # The '%' indicator is already checked. + if self.column == 0: + return True + + def check_document_start(self): + + # DOCUMENT-START: ^ '---' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '---' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_document_end(self): + + # DOCUMENT-END: ^ '...' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '...' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_block_entry(self): + + # BLOCK-ENTRY: '-' (' '|'\n') + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_key(self): + + # KEY(flow context): '?' + if self.flow_level: + return True + + # KEY(block context): '?' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_value(self): + + # VALUE(flow context): ':' + if self.flow_level: + return True + + # VALUE(block context): ':' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_plain(self): + + # A plain scalar may start with any non-space character except: + # '-', '?', ':', ',', '[', ']', '{', '}', + # '#', '&', '*', '!', '|', '>', '\'', '\"', + # '%', '@', '`'. + # + # It may also start with + # '-', '?', ':' + # if it is followed by a non-space character. + # + # Note that we limit the last rule to the block context (except the + # '-' character) because we want the flow context to be space + # independent. + ch = self.peek() + return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ + or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' + and (ch == '-' or (not self.flow_level and ch in '?:'))) + + # Scanners. + + def scan_to_next_token(self): + # We ignore spaces, line breaks and comments. + # If we find a line break in the block context, we set the flag + # `allow_simple_key` on. + # The byte order mark is stripped if it's the first character in the + # stream. We do not yet support BOM inside the stream as the + # specification requires. Any such mark will be considered as a part + # of the document. + # + # TODO: We need to make tab handling rules more sane. A good rule is + # Tabs cannot precede tokens + # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END, + # KEY(block), VALUE(block), BLOCK-ENTRY + # So the checking code is + # if : + # self.allow_simple_keys = False + # We also need to add the check for `allow_simple_keys == True` to + # `unwind_indent` before issuing BLOCK-END. + # Scanners for block, flow, and plain scalars need to be modified. + + if self.index == 0 and self.peek() == '\uFEFF': + self.forward() + found = False + while not found: + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + if self.scan_line_break(): + if not self.flow_level: + self.allow_simple_key = True + else: + found = True + + def scan_directive(self): + # See the specification for details. + start_mark = self.get_mark() + self.forward() + name = self.scan_directive_name(start_mark) + value = None + if name == 'YAML': + value = self.scan_yaml_directive_value(start_mark) + end_mark = self.get_mark() + elif name == 'TAG': + value = self.scan_tag_directive_value(start_mark) + end_mark = self.get_mark() + else: + end_mark = self.get_mark() + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + self.scan_directive_ignored_line(start_mark) + return DirectiveToken(name, value, start_mark, end_mark) + + def scan_directive_name(self, start_mark): + # See the specification for details. + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + return value + + def scan_yaml_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + major = self.scan_yaml_directive_number(start_mark) + if self.peek() != '.': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or '.', but found %r" % self.peek(), + self.get_mark()) + self.forward() + minor = self.scan_yaml_directive_number(start_mark) + if self.peek() not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or ' ', but found %r" % self.peek(), + self.get_mark()) + return (major, minor) + + def scan_yaml_directive_number(self, start_mark): + # See the specification for details. + ch = self.peek() + if not ('0' <= ch <= '9'): + raise ScannerError("while scanning a directive", start_mark, + "expected a digit, but found %r" % ch, self.get_mark()) + length = 0 + while '0' <= self.peek(length) <= '9': + length += 1 + value = int(self.prefix(length)) + self.forward(length) + return value + + def scan_tag_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + handle = self.scan_tag_directive_handle(start_mark) + while self.peek() == ' ': + self.forward() + prefix = self.scan_tag_directive_prefix(start_mark) + return (handle, prefix) + + def scan_tag_directive_handle(self, start_mark): + # See the specification for details. + value = self.scan_tag_handle('directive', start_mark) + ch = self.peek() + if ch != ' ': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_tag_directive_prefix(self, start_mark): + # See the specification for details. + value = self.scan_tag_uri('directive', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_directive_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a comment or a line break, but found %r" + % ch, self.get_mark()) + self.scan_line_break() + + def scan_anchor(self, TokenClass): + # The specification does not restrict characters for anchors and + # aliases. This may lead to problems, for instance, the document: + # [ *alias, value ] + # can be interpreted in two ways, as + # [ "value" ] + # and + # [ *alias , "value" ] + # Therefore we restrict aliases to numbers and ASCII letters. + start_mark = self.get_mark() + indicator = self.peek() + if indicator == '*': + name = 'alias' + else: + name = 'anchor' + self.forward() + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`': + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + end_mark = self.get_mark() + return TokenClass(value, start_mark, end_mark) + + def scan_tag(self): + # See the specification for details. + start_mark = self.get_mark() + ch = self.peek(1) + if ch == '<': + handle = None + self.forward(2) + suffix = self.scan_tag_uri('tag', start_mark) + if self.peek() != '>': + raise ScannerError("while parsing a tag", start_mark, + "expected '>', but found %r" % self.peek(), + self.get_mark()) + self.forward() + elif ch in '\0 \t\r\n\x85\u2028\u2029': + handle = None + suffix = '!' + self.forward() + else: + length = 1 + use_handle = False + while ch not in '\0 \r\n\x85\u2028\u2029': + if ch == '!': + use_handle = True + break + length += 1 + ch = self.peek(length) + handle = '!' + if use_handle: + handle = self.scan_tag_handle('tag', start_mark) + else: + handle = '!' + self.forward() + suffix = self.scan_tag_uri('tag', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a tag", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + value = (handle, suffix) + end_mark = self.get_mark() + return TagToken(value, start_mark, end_mark) + + def scan_block_scalar(self, style): + # See the specification for details. + + if style == '>': + folded = True + else: + folded = False + + chunks = [] + start_mark = self.get_mark() + + # Scan the header. + self.forward() + chomping, increment = self.scan_block_scalar_indicators(start_mark) + self.scan_block_scalar_ignored_line(start_mark) + + # Determine the indentation level and go to the first non-empty line. + min_indent = self.indent+1 + if min_indent < 1: + min_indent = 1 + if increment is None: + breaks, max_indent, end_mark = self.scan_block_scalar_indentation() + indent = max(min_indent, max_indent) + else: + indent = min_indent+increment-1 + breaks, end_mark = self.scan_block_scalar_breaks(indent) + line_break = '' + + # Scan the inner part of the block scalar. + while self.column == indent and self.peek() != '\0': + chunks.extend(breaks) + leading_non_space = self.peek() not in ' \t' + length = 0 + while self.peek(length) not in '\0\r\n\x85\u2028\u2029': + length += 1 + chunks.append(self.prefix(length)) + self.forward(length) + line_break = self.scan_line_break() + breaks, end_mark = self.scan_block_scalar_breaks(indent) + if self.column == indent and self.peek() != '\0': + + # Unfortunately, folding rules are ambiguous. + # + # This is the folding according to the specification: + + if folded and line_break == '\n' \ + and leading_non_space and self.peek() not in ' \t': + if not breaks: + chunks.append(' ') + else: + chunks.append(line_break) + + # This is Clark Evans's interpretation (also in the spec + # examples): + # + #if folded and line_break == '\n': + # if not breaks: + # if self.peek() not in ' \t': + # chunks.append(' ') + # else: + # chunks.append(line_break) + #else: + # chunks.append(line_break) + else: + break + + # Chomp the tail. + if chomping is not False: + chunks.append(line_break) + if chomping is True: + chunks.extend(breaks) + + # We are done. + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + def scan_block_scalar_indicators(self, start_mark): + # See the specification for details. + chomping = None + increment = None + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + elif ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected chomping or indentation indicators, but found %r" + % ch, self.get_mark()) + return chomping, increment + + def scan_block_scalar_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected a comment or a line break, but found %r" % ch, + self.get_mark()) + self.scan_line_break() + + def scan_block_scalar_indentation(self): + # See the specification for details. + chunks = [] + max_indent = 0 + end_mark = self.get_mark() + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() != ' ': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + else: + self.forward() + if self.column > max_indent: + max_indent = self.column + return chunks, max_indent, end_mark + + def scan_block_scalar_breaks(self, indent): + # See the specification for details. + chunks = [] + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + while self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + return chunks, end_mark + + def scan_flow_scalar(self, style): + # See the specification for details. + # Note that we loose indentation rules for quoted scalars. Quoted + # scalars don't need to adhere indentation because " and ' clearly + # mark the beginning and the end of them. Therefore we are less + # restrictive then the specification requires. We only need to check + # that document separators are not included in scalars. + if style == '"': + double = True + else: + double = False + chunks = [] + start_mark = self.get_mark() + quote = self.peek() + self.forward() + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + while self.peek() != quote: + chunks.extend(self.scan_flow_scalar_spaces(double, start_mark)) + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + self.forward() + end_mark = self.get_mark() + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + ESCAPE_REPLACEMENTS = { + '0': '\0', + 'a': '\x07', + 'b': '\x08', + 't': '\x09', + '\t': '\x09', + 'n': '\x0A', + 'v': '\x0B', + 'f': '\x0C', + 'r': '\x0D', + 'e': '\x1B', + ' ': '\x20', + '\"': '\"', + '\\': '\\', + '/': '/', + 'N': '\x85', + '_': '\xA0', + 'L': '\u2028', + 'P': '\u2029', + } + + ESCAPE_CODES = { + 'x': 2, + 'u': 4, + 'U': 8, + } + + def scan_flow_scalar_non_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + length = 0 + while self.peek(length) not in '\'\"\\\0 \t\r\n\x85\u2028\u2029': + length += 1 + if length: + chunks.append(self.prefix(length)) + self.forward(length) + ch = self.peek() + if not double and ch == '\'' and self.peek(1) == '\'': + chunks.append('\'') + self.forward(2) + elif (double and ch == '\'') or (not double and ch in '\"\\'): + chunks.append(ch) + self.forward() + elif double and ch == '\\': + self.forward() + ch = self.peek() + if ch in self.ESCAPE_REPLACEMENTS: + chunks.append(self.ESCAPE_REPLACEMENTS[ch]) + self.forward() + elif ch in self.ESCAPE_CODES: + length = self.ESCAPE_CODES[ch] + self.forward() + for k in range(length): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "expected escape sequence of %d hexadecimal numbers, but found %r" % + (length, self.peek(k)), self.get_mark()) + code = int(self.prefix(length), 16) + chunks.append(chr(code)) + self.forward(length) + elif ch in '\r\n\x85\u2028\u2029': + self.scan_line_break() + chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) + else: + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "found unknown escape character %r" % ch, self.get_mark()) + else: + return chunks + + def scan_flow_scalar_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + length = 0 + while self.peek(length) in ' \t': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch == '\0': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected end of stream", self.get_mark()) + elif ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + breaks = self.scan_flow_scalar_breaks(double, start_mark) + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + else: + chunks.append(whitespaces) + return chunks + + def scan_flow_scalar_breaks(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + # Instead of checking indentation, we check for document + # separators. + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected document separator", self.get_mark()) + while self.peek() in ' \t': + self.forward() + if self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + else: + return chunks + + def scan_plain(self): + # See the specification for details. + # We add an additional restriction for the flow context: + # plain scalars in the flow context cannot contain ',' or '?'. + # We also keep track of the `allow_simple_key` flag here. + # Indentation rules are loosed for the flow context. + chunks = [] + start_mark = self.get_mark() + end_mark = start_mark + indent = self.indent+1 + # We allow zero indentation for scalars, but then we need to check for + # document separators at the beginning of the line. + #if indent == 0: + # indent = 1 + spaces = [] + while True: + length = 0 + if self.peek() == '#': + break + while True: + ch = self.peek(length) + if ch in '\0 \t\r\n\x85\u2028\u2029' \ + or (ch == ':' and + self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' + + (u',[]{}' if self.flow_level else u''))\ + or (self.flow_level and ch in ',?[]{}'): + break + length += 1 + if length == 0: + break + self.allow_simple_key = False + chunks.extend(spaces) + chunks.append(self.prefix(length)) + self.forward(length) + end_mark = self.get_mark() + spaces = self.scan_plain_spaces(indent, start_mark) + if not spaces or self.peek() == '#' \ + or (not self.flow_level and self.column < indent): + break + return ScalarToken(''.join(chunks), True, start_mark, end_mark) + + def scan_plain_spaces(self, indent, start_mark): + # See the specification for details. + # The specification is really confusing about tabs in plain scalars. + # We just forbid them completely. Do not use tabs in YAML! + chunks = [] + length = 0 + while self.peek(length) in ' ': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + self.allow_simple_key = True + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + breaks = [] + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() == ' ': + self.forward() + else: + breaks.append(self.scan_line_break()) + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + elif whitespaces: + chunks.append(whitespaces) + return chunks + + def scan_tag_handle(self, name, start_mark): + # See the specification for details. + # For some strange reasons, the specification does not allow '_' in + # tag handles. I have allowed it anyway. + ch = self.peek() + if ch != '!': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length = 1 + ch = self.peek(length) + if ch != ' ': + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if ch != '!': + self.forward(length) + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length += 1 + value = self.prefix(length) + self.forward(length) + return value + + def scan_tag_uri(self, name, start_mark): + # See the specification for details. + # Note: we do not check if URI is well-formed. + chunks = [] + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.!~*\'()[]%': + if ch == '%': + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + chunks.append(self.scan_uri_escapes(name, start_mark)) + else: + length += 1 + ch = self.peek(length) + if length: + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + if not chunks: + raise ScannerError("while parsing a %s" % name, start_mark, + "expected URI, but found %r" % ch, self.get_mark()) + return ''.join(chunks) + + def scan_uri_escapes(self, name, start_mark): + # See the specification for details. + codes = [] + mark = self.get_mark() + while self.peek() == '%': + self.forward() + for k in range(2): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected URI escape sequence of 2 hexadecimal numbers, but found %r" + % self.peek(k), self.get_mark()) + codes.append(int(self.prefix(2), 16)) + self.forward(2) + try: + value = bytes(codes).decode('utf-8') + except UnicodeDecodeError as exc: + raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark) + return value + + def scan_line_break(self): + # Transforms: + # '\r\n' : '\n' + # '\r' : '\n' + # '\n' : '\n' + # '\x85' : '\n' + # '\u2028' : '\u2028' + # '\u2029 : '\u2029' + # default : '' + ch = self.peek() + if ch in '\r\n\x85': + if self.prefix(2) == '\r\n': + self.forward(2) + else: + self.forward() + return '\n' + elif ch in '\u2028\u2029': + self.forward() + return ch + return '' diff --git a/testing/prajintry/pacakges/yaml/serializer.py b/testing/prajintry/pacakges/yaml/serializer.py new file mode 100644 index 00000000..fe911e67 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/serializer.py @@ -0,0 +1,111 @@ + +__all__ = ['Serializer', 'SerializerError'] + +from .error import YAMLError +from .events import * +from .nodes import * + +class SerializerError(YAMLError): + pass + +class Serializer: + + ANCHOR_TEMPLATE = 'id%03d' + + def __init__(self, encoding=None, + explicit_start=None, explicit_end=None, version=None, tags=None): + self.use_encoding = encoding + self.use_explicit_start = explicit_start + self.use_explicit_end = explicit_end + self.use_version = version + self.use_tags = tags + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + self.closed = None + + def open(self): + if self.closed is None: + self.emit(StreamStartEvent(encoding=self.use_encoding)) + self.closed = False + elif self.closed: + raise SerializerError("serializer is closed") + else: + raise SerializerError("serializer is already opened") + + def close(self): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif not self.closed: + self.emit(StreamEndEvent()) + self.closed = True + + #def __del__(self): + # self.close() + + def serialize(self, node): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif self.closed: + raise SerializerError("serializer is closed") + self.emit(DocumentStartEvent(explicit=self.use_explicit_start, + version=self.use_version, tags=self.use_tags)) + self.anchor_node(node) + self.serialize_node(node, None, None) + self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + + def anchor_node(self, node): + if node in self.anchors: + if self.anchors[node] is None: + self.anchors[node] = self.generate_anchor(node) + else: + self.anchors[node] = None + if isinstance(node, SequenceNode): + for item in node.value: + self.anchor_node(item) + elif isinstance(node, MappingNode): + for key, value in node.value: + self.anchor_node(key) + self.anchor_node(value) + + def generate_anchor(self, node): + self.last_anchor_id += 1 + return self.ANCHOR_TEMPLATE % self.last_anchor_id + + def serialize_node(self, node, parent, index): + alias = self.anchors[node] + if node in self.serialized_nodes: + self.emit(AliasEvent(alias)) + else: + self.serialized_nodes[node] = True + self.descend_resolver(parent, index) + if isinstance(node, ScalarNode): + detected_tag = self.resolve(ScalarNode, node.value, (True, False)) + default_tag = self.resolve(ScalarNode, node.value, (False, True)) + implicit = (node.tag == detected_tag), (node.tag == default_tag) + self.emit(ScalarEvent(alias, node.tag, implicit, node.value, + style=node.style)) + elif isinstance(node, SequenceNode): + implicit = (node.tag + == self.resolve(SequenceNode, node.value, True)) + self.emit(SequenceStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + index = 0 + for item in node.value: + self.serialize_node(item, node, index) + index += 1 + self.emit(SequenceEndEvent()) + elif isinstance(node, MappingNode): + implicit = (node.tag + == self.resolve(MappingNode, node.value, True)) + self.emit(MappingStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + for key, value in node.value: + self.serialize_node(key, node, None) + self.serialize_node(value, node, key) + self.emit(MappingEndEvent()) + self.ascend_resolver() + diff --git a/testing/prajintry/pacakges/yaml/tokens.py b/testing/prajintry/pacakges/yaml/tokens.py new file mode 100644 index 00000000..4d0b48a3 --- /dev/null +++ b/testing/prajintry/pacakges/yaml/tokens.py @@ -0,0 +1,104 @@ + +class Token(object): + def __init__(self, start_mark, end_mark): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in self.__dict__ + if not key.endswith('_mark')] + attributes.sort() + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +#class BOMToken(Token): +# id = '' + +class DirectiveToken(Token): + id = '' + def __init__(self, name, value, start_mark, end_mark): + self.name = name + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class DocumentStartToken(Token): + id = '' + +class DocumentEndToken(Token): + id = '' + +class StreamStartToken(Token): + id = '' + def __init__(self, start_mark=None, end_mark=None, + encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndToken(Token): + id = '' + +class BlockSequenceStartToken(Token): + id = '' + +class BlockMappingStartToken(Token): + id = '' + +class BlockEndToken(Token): + id = '' + +class FlowSequenceStartToken(Token): + id = '[' + +class FlowMappingStartToken(Token): + id = '{' + +class FlowSequenceEndToken(Token): + id = ']' + +class FlowMappingEndToken(Token): + id = '}' + +class KeyToken(Token): + id = '?' + +class ValueToken(Token): + id = ':' + +class BlockEntryToken(Token): + id = '-' + +class FlowEntryToken(Token): + id = ',' + +class AliasToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class AnchorToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class TagToken(Token): + id = '' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class ScalarToken(Token): + id = '' + def __init__(self, value, plain, start_mark, end_mark, style=None): + self.value = value + self.plain = plain + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + diff --git a/testing/prajintry/trying.zip b/testing/prajintry/trying.zip new file mode 100644 index 0000000000000000000000000000000000000000..29c13e68cdf2ca6d08a2330658d6d19bd7aaca31 GIT binary patch literal 263282 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 C#Bme= literal 0 HcmV?d00001 diff --git a/testing/prajintry/user.py b/testing/prajintry/user.py new file mode 100644 index 00000000..429f3f8f --- /dev/null +++ b/testing/prajintry/user.py @@ -0,0 +1,2 @@ +def main(): + return "ABC" From 9106337c79ddfe12ea4518d87b0783d11bc37945 Mon Sep 17 00:00:00 2001 From: prajinkhadka Date: Mon, 5 Aug 2024 22:31:03 -0500 Subject: [PATCH 43/47] WIP -> Main Functionality is working. Need to optimize the code and remove comments --- benchmarks/100.webapps/120.uploader/python/function.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmarks/100.webapps/120.uploader/python/function.py b/benchmarks/100.webapps/120.uploader/python/function.py index 6ef23580..ae8e1882 100755 --- a/benchmarks/100.webapps/120.uploader/python/function.py +++ b/benchmarks/100.webapps/120.uploader/python/function.py @@ -7,6 +7,8 @@ # from . import storage # client = storage.storage.get_instance() +# PK: IN fission the above import is not working because of directroy structure. +# Need to rethink how to do this from storage import storage client = storage.get_instance() From 25d98dd191b78680c0a48eea8969e9aee10f72a1 Mon Sep 17 00:00:00 2001 From: prajinkhadka Date: Mon, 5 Aug 2024 22:44:42 -0500 Subject: [PATCH 44/47] WIP -> Pass Minio storage config as env variables. --- config/example.json | 6 +- experiments.json | 182 ++++++++++++++++++++-------------------- out_storage.json | 6 +- sebs/fission/config.py | 12 +-- sebs/fission/fission.py | 26 +++--- 5 files changed, 114 insertions(+), 118 deletions(-) diff --git a/config/example.json b/config/example.json index c5a82263..b6356700 100644 --- a/config/example.json +++ b/config/example.json @@ -101,9 +101,9 @@ "storage": { "address": "192.168.0.128:9011", "mapped_port": 9011, - "access_key": "nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", - "secret_key": "b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", - "instance_id": "4e167c8953ba293e1237313256e3591e4babd9bf0688ead694e20c24ca3b7b7d", + "access_key": "p1a-VpGMCOw0UX4b5LxD2zKVWklMjTVNhJxk6UiSpCg", + "secret_key": "a40939431705f9facfe11a598fefc500d12f44d898d82709347e6802daf1b257", + "instance_id": "a83363e7e61072c5b75385357086abcb5b26df9e6baf5dc34ccbc39c2d0a98ad", "input_buckets": [], "output_buckets": [], "type": "minio" diff --git a/experiments.json b/experiments.json index 957fbea1..a4819074 100644 --- a/experiments.json +++ b/experiments.json @@ -1,30 +1,30 @@ { "_invocations": { "120uploader-python-38": { - "012fe446-6a86-4f8c-b3ed-e12ab3fd677c": { + "53084c7d-11d4-4e53-a351-f2a65af52637": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722905362.327837", + "begin": "1722915836.646448", "cold_start_var": "", - "container_id": "9e75ae79", - "end": "1722905362.646886", - "is_cold": false, - "request_id": "012fe446-6a86-4f8c-b3ed-e12ab3fd677c", + "container_id": "366f045f", + "end": "1722915837.281968", + "is_cold": true, + "request_id": "53084c7d-11d4-4e53-a351-f2a65af52637", "result": { "measurement": { - "compute_time": 304484.0, + "compute_time": 338464.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 14510.0 + "upload_time": 33736.0 }, "output": { - "bucket": "sebs-benchmarks-a7e49de0", - "key": "120.uploader-0-output/800px-Jammlich_crop.406cf87b.jpg", + "bucket": "sebs-benchmarks-0e4d1946", + "key": "120.uploader-0-output/800px-Jammlich_crop.2188af9e.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -35,46 +35,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "012fe446-6a86-4f8c-b3ed-e12ab3fd677c", + "request_id": "53084c7d-11d4-4e53-a351-f2a65af52637", "stats": { - "cold_start": false, + "cold_start": true, "failure": false, "memory_used": null }, "times": { - "benchmark": 319049, - "client": 329378, - "client_begin": "2024-08-05 19:49:22.347862", - "client_end": "2024-08-05 19:49:22.677240", - "http_first_byte_return": 0.328978, - "http_startup": 0.000804, + "benchmark": 635520, + "client": 7516466, + "client_begin": "2024-08-05 22:43:49.769421", + "client_end": "2024-08-05 22:43:57.285887", + "http_first_byte_return": 7.516239, + "http_startup": 0.000356, "initialization": 0 } }, - "0efb7ab0-5366-49a3-857f-118fc07f0437": { + "6d125e98-539b-4d31-b4da-b3b7b64e1fce": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722905360.906010", + "begin": "1722915838.147054", "cold_start_var": "", - "container_id": "9e75ae79", - "end": "1722905361.561434", - "is_cold": true, - "request_id": "0efb7ab0-5366-49a3-857f-118fc07f0437", + "container_id": "3c8933e5", + "end": "1722915838.499241", + "is_cold": false, + "request_id": "6d125e98-539b-4d31-b4da-b3b7b64e1fce", "result": { "measurement": { - "compute_time": 327144.0, + "compute_time": 317308.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 16698.0 + "upload_time": 34838.0 }, "output": { - "bucket": "sebs-benchmarks-a7e49de0", - "key": "120.uploader-0-output/800px-Jammlich_crop.a31eec16.jpg", + "bucket": "sebs-benchmarks-0e4d1946", + "key": "120.uploader-0-output/800px-Jammlich_crop.dd5ffb04.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -85,46 +85,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "0efb7ab0-5366-49a3-857f-118fc07f0437", + "request_id": "6d125e98-539b-4d31-b4da-b3b7b64e1fce", "stats": { - "cold_start": true, + "cold_start": false, "failure": false, "memory_used": null }, "times": { - "benchmark": 655424, - "client": 931981, - "client_begin": "2024-08-05 19:49:20.661627", - "client_end": "2024-08-05 19:49:21.593608", - "http_first_byte_return": 0.931417, - "http_startup": 0.000735, + "benchmark": 352187, + "client": 396140, + "client_begin": "2024-08-05 22:43:58.107543", + "client_end": "2024-08-05 22:43:58.503683", + "http_first_byte_return": 0.39601, + "http_startup": 0.00051, "initialization": 0 } }, - "23a5de95-2445-4fe6-8fed-7c8735322444": { + "a3d417cd-4610-435b-ab1c-82f4ad1fcff0": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722905359.845015", + "begin": "1722915837.502903", "cold_start_var": "", - "container_id": "8deea8d1", - "end": "1722905360.607545", + "container_id": "3c8933e5", + "end": "1722915838.101299", "is_cold": true, - "request_id": "23a5de95-2445-4fe6-8fed-7c8735322444", + "request_id": "a3d417cd-4610-435b-ab1c-82f4ad1fcff0", "result": { "measurement": { - "compute_time": 356009.0, + "compute_time": 325414.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 68016.0 + "upload_time": 21113.0 }, "output": { - "bucket": "sebs-benchmarks-a7e49de0", - "key": "120.uploader-0-output/800px-Jammlich_crop.e6eb0ea9.jpg", + "bucket": "sebs-benchmarks-0e4d1946", + "key": "120.uploader-0-output/800px-Jammlich_crop.e7782608.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -135,46 +135,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "23a5de95-2445-4fe6-8fed-7c8735322444", + "request_id": "a3d417cd-4610-435b-ab1c-82f4ad1fcff0", "stats": { "cold_start": true, "failure": false, "memory_used": null }, "times": { - "benchmark": 762530, - "client": 4735457, - "client_begin": "2024-08-05 19:49:15.909896", - "client_end": "2024-08-05 19:49:20.645353", - "http_first_byte_return": 4.733943, - "http_startup": 0.000301, + "benchmark": 598396, + "client": 816197, + "client_begin": "2024-08-05 22:43:57.289957", + "client_end": "2024-08-05 22:43:58.106154", + "http_first_byte_return": 0.815869, + "http_startup": 0.000385, "initialization": 0 } }, - "91508113-fdbc-439f-aee6-409066e1f6e4": { + "ebf954cb-a757-432d-a6ea-2c7fc501e5b5": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722905361.999195", + "begin": "1722915838.849627", "cold_start_var": "", - "container_id": "9e75ae79", - "end": "1722905362.314820", + "container_id": "3c8933e5", + "end": "1722915839.187322", "is_cold": false, - "request_id": "91508113-fdbc-439f-aee6-409066e1f6e4", + "request_id": "ebf954cb-a757-432d-a6ea-2c7fc501e5b5", "result": { "measurement": { - "compute_time": 302069.0, + "compute_time": 323360.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 13410.0 + "upload_time": 14292.0 }, "output": { - "bucket": "sebs-benchmarks-a7e49de0", - "key": "120.uploader-0-output/800px-Jammlich_crop.31af2db3.jpg", + "bucket": "sebs-benchmarks-0e4d1946", + "key": "120.uploader-0-output/800px-Jammlich_crop.7ee491ee.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -185,46 +185,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "91508113-fdbc-439f-aee6-409066e1f6e4", + "request_id": "ebf954cb-a757-432d-a6ea-2c7fc501e5b5", "stats": { "cold_start": false, "failure": false, "memory_used": null }, "times": { - "benchmark": 315625, - "client": 351370, - "client_begin": "2024-08-05 19:49:21.995459", - "client_end": "2024-08-05 19:49:22.346829", - "http_first_byte_return": 0.350798, - "http_startup": 0.000537, + "benchmark": 337695, + "client": 343333, + "client_begin": "2024-08-05 22:43:58.847267", + "client_end": "2024-08-05 22:43:59.190600", + "http_first_byte_return": 0.343104, + "http_startup": 0.000342, "initialization": 0 } }, - "9a0dc19f-91e4-40d7-a859-6d58d4c3a321": { + "ffd6f664-ab19-415e-bcbe-dd228427f6b3": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722905361.593417", + "begin": "1722915838.508601", "cold_start_var": "", - "container_id": "9e75ae79", - "end": "1722905361.960123", + "container_id": "3c8933e5", + "end": "1722915838.844937", "is_cold": false, - "request_id": "9a0dc19f-91e4-40d7-a859-6d58d4c3a321", + "request_id": "ffd6f664-ab19-415e-bcbe-dd228427f6b3", "result": { "measurement": { - "compute_time": 353580.0, + "compute_time": 328533.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 13074.0 + "upload_time": 7756.0 }, "output": { - "bucket": "sebs-benchmarks-a7e49de0", - "key": "120.uploader-0-output/800px-Jammlich_crop.996d77ff.jpg", + "bucket": "sebs-benchmarks-0e4d1946", + "key": "120.uploader-0-output/800px-Jammlich_crop.9e6e2a56.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -235,26 +235,26 @@ "execution": 0, "initialization": 0 }, - "request_id": "9a0dc19f-91e4-40d7-a859-6d58d4c3a321", + "request_id": "ffd6f664-ab19-415e-bcbe-dd228427f6b3", "stats": { "cold_start": false, "failure": false, "memory_used": null }, "times": { - "benchmark": 366706, - "client": 394377, - "client_begin": "2024-08-05 19:49:21.600371", - "client_end": "2024-08-05 19:49:21.994748", - "http_first_byte_return": 0.393647, - "http_startup": 0.005644, + "benchmark": 336336, + "client": 342241, + "client_begin": "2024-08-05 22:43:58.504423", + "client_end": "2024-08-05 22:43:58.846664", + "http_first_byte_return": 0.342163, + "http_startup": 0.000406, "initialization": 0 } } } }, "_metrics": {}, - "begin_time": 1722905355.833009, + "begin_time": 1722915829.707452, "config": { "deployment": { "credentials": {}, @@ -262,19 +262,19 @@ "name": "fission", "removeCluster": false, "resources": { - "benchmarks": "sebs-benchmarks-a7e49de0", + "benchmarks": "sebs-benchmarks-0e4d1946", "docker_password": "", "docker_registry": "", "docker_username": "", - "resources_id": "a7e49de0", + "resources_id": "0e4d1946", "storage": { - "access_key": "nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", + "access_key": "p1a-VpGMCOw0UX4b5LxD2zKVWklMjTVNhJxk6UiSpCg", "address": "192.168.0.128:9011", "input_buckets": [], - "instance_id": "4e167c8953ba293e1237313256e3591e4babd9bf0688ead694e20c24ca3b7b7d", + "instance_id": "a83363e7e61072c5b75385357086abcb5b26df9e6baf5dc34ccbc39c2d0a98ad", "mapped_port": 9011, "output_buckets": [], - "secret_key": "b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", + "secret_key": "a40939431705f9facfe11a598fefc500d12f44d898d82709347e6802daf1b257", "type": "minio" } }, @@ -331,6 +331,6 @@ "update_storage": false } }, - "end_time": 1722905362.677655, + "end_time": 1722915839.191321, "result_bucket": null } \ No newline at end of file diff --git a/out_storage.json b/out_storage.json index 2b6e1586..25e1485f 100644 --- a/out_storage.json +++ b/out_storage.json @@ -1,9 +1,9 @@ { "address": "localhost:9011", "mapped_port": 9011, - "access_key": "nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", - "secret_key": "b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", - "instance_id": "4e167c8953ba293e1237313256e3591e4babd9bf0688ead694e20c24ca3b7b7d", + "access_key": "p1a-VpGMCOw0UX4b5LxD2zKVWklMjTVNhJxk6UiSpCg", + "secret_key": "a40939431705f9facfe11a598fefc500d12f44d898d82709347e6802daf1b257", + "instance_id": "a83363e7e61072c5b75385357086abcb5b26df9e6baf5dc34ccbc39c2d0a98ad", "output_buckets": [], "input_buckets": [], "type": "minio" diff --git a/sebs/fission/config.py b/sebs/fission/config.py index 96dfd856..5221e479 100644 --- a/sebs/fission/config.py +++ b/sebs/fission/config.py @@ -211,22 +211,12 @@ def create_enviroment(self, name: str, image: str, builder: str, runtime_env: Li # ret.logging.info(f'Creating env for {name} using image "{image}".') print(f'Creating env for {name} using image "{image}".') try: - # PK: Testing - runtime_env = [ - "MINIO_STORAGE_SECRET_KEY=b4298ae315d1204a6ce2d9bb309bcc6e5f65e4a251ab7213fdbf25bcb2cbb6b0", - "MINIO_STORAGE_ACCESS_KEY=nGUIrfwgqm0OOSuTDBbFrlJ857hvj1RmCduNG_fA5fs", - "MINIO_STORAGE_CONNECTION_URL=192.168.0.128:9011" - ] - connection_uri = runtime_env[2] access_key = runtime_env[1] secret_key = runtime_env[0] - print(f"fission env create --name {name} --image {image} --builder {builder} --runtime-env {connection_uri} --runtime-env {access_key} --runtime-env {secret_key}") - print("The runtime env is", runtime_env) - exit(0) subprocess.run( f"fission env create --name {name} --image {image} \ - --builder {builder} --runtime-env {connection_uri} --runtime-env {access_key} --runtime-env {secret_key}".split(), + --builder {builder} --runtime-env {runtime_env[2]} --runtime-env {runtime_env[1]} --runtime-env {runtime_env[0]}".split(), check=True, stdout=subprocess.DEVNULL, ) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 85b72e78..6c81f5d5 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -287,16 +287,22 @@ def package_code( def storage_arguments(self) -> List[str]: storage = cast(Minio, self.get_storage()) return [ - "--runtime-env", - "MINIO_STORAGE_SECRET_KEY", - storage.config.secret_key, - "--runtime-env", - "MINIO_STORAGE_ACCESS_KEY", - storage.config.access_key, - "--runtime-env", - "MINIO_STORAGE_CONNECTION_URL", - storage.config.address, - ] + f"MINIO_STORAGE_SECRET_KEY={storage.config.secret_key}", + f"MINIO_STORAGE_ACCESS_KEY={storage.config.access_key}", + f"MINIO_STORAGE_CONNECTION_URL={storage.config.address}" + ] + + # return [ + # "--runtime-env", + # "MINIO_STORAGE_SECRET_KEY", + # storage.config.secret_key, + # "--runtime-env", + # "MINIO_STORAGE_ACCESS_KEY", + # storage.config.access_key, + # "--runtime-env", + # "MINIO_STORAGE_CONNECTION_URL", + # storage.config.address, + # ] def create_function(self, code_package: Benchmark, func_name: str) -> "FissionFunction": From fcf22cc2db1f841394b15a8858445ce8418e090c Mon Sep 17 00:00:00 2001 From: prajinkhadka Date: Mon, 5 Aug 2024 23:09:12 -0500 Subject: [PATCH 45/47] WIP -> Do not change funcion.py anywhere --- .../120.uploader/python/function.py | 8 +- experiments.json | 180 +++++++++--------- sebs/fission/fission.py | 29 ++- 3 files changed, 114 insertions(+), 103 deletions(-) diff --git a/benchmarks/100.webapps/120.uploader/python/function.py b/benchmarks/100.webapps/120.uploader/python/function.py index ae8e1882..c13656d0 100755 --- a/benchmarks/100.webapps/120.uploader/python/function.py +++ b/benchmarks/100.webapps/120.uploader/python/function.py @@ -5,12 +5,8 @@ import urllib.request -# from . import storage -# client = storage.storage.get_instance() -# PK: IN fission the above import is not working because of directroy structure. -# Need to rethink how to do this -from storage import storage -client = storage.get_instance() +from . import storage +client = storage.storage.get_instance() def handler(event): diff --git a/experiments.json b/experiments.json index a4819074..85ff55eb 100644 --- a/experiments.json +++ b/experiments.json @@ -1,30 +1,30 @@ { "_invocations": { "120uploader-python-38": { - "53084c7d-11d4-4e53-a351-f2a65af52637": { + "2f2dc04d-f7e3-4ae8-8724-c54170995df8": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722915836.646448", + "begin": "1722917285.352187", "cold_start_var": "", - "container_id": "366f045f", - "end": "1722915837.281968", - "is_cold": true, - "request_id": "53084c7d-11d4-4e53-a351-f2a65af52637", + "container_id": "d0159c55", + "end": "1722917285.669119", + "is_cold": false, + "request_id": "2f2dc04d-f7e3-4ae8-8724-c54170995df8", "result": { "measurement": { - "compute_time": 338464.0, + "compute_time": 304905.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 33736.0 + "upload_time": 11975.0 }, "output": { - "bucket": "sebs-benchmarks-0e4d1946", - "key": "120.uploader-0-output/800px-Jammlich_crop.2188af9e.jpg", + "bucket": "sebs-benchmarks-6b073204", + "key": "120.uploader-0-output/800px-Jammlich_crop.3e01864e.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -35,46 +35,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "53084c7d-11d4-4e53-a351-f2a65af52637", + "request_id": "2f2dc04d-f7e3-4ae8-8724-c54170995df8", "stats": { - "cold_start": true, + "cold_start": false, "failure": false, "memory_used": null }, "times": { - "benchmark": 635520, - "client": 7516466, - "client_begin": "2024-08-05 22:43:49.769421", - "client_end": "2024-08-05 22:43:57.285887", - "http_first_byte_return": 7.516239, - "http_startup": 0.000356, + "benchmark": 316932, + "client": 324899, + "client_begin": "2024-08-05 23:08:05.349099", + "client_end": "2024-08-05 23:08:05.673998", + "http_first_byte_return": 0.324788, + "http_startup": 0.000415, "initialization": 0 } }, - "6d125e98-539b-4d31-b4da-b3b7b64e1fce": { + "42cba73d-ea93-4017-8e47-14c1b8aea1c7": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722915838.147054", + "begin": "1722917285.030580", "cold_start_var": "", - "container_id": "3c8933e5", - "end": "1722915838.499241", + "container_id": "d0159c55", + "end": "1722917285.345622", "is_cold": false, - "request_id": "6d125e98-539b-4d31-b4da-b3b7b64e1fce", + "request_id": "42cba73d-ea93-4017-8e47-14c1b8aea1c7", "result": { "measurement": { - "compute_time": 317308.0, + "compute_time": 302574.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 34838.0 + "upload_time": 12427.0 }, "output": { - "bucket": "sebs-benchmarks-0e4d1946", - "key": "120.uploader-0-output/800px-Jammlich_crop.dd5ffb04.jpg", + "bucket": "sebs-benchmarks-6b073204", + "key": "120.uploader-0-output/800px-Jammlich_crop.6781c9d7.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -85,46 +85,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "6d125e98-539b-4d31-b4da-b3b7b64e1fce", + "request_id": "42cba73d-ea93-4017-8e47-14c1b8aea1c7", "stats": { "cold_start": false, "failure": false, "memory_used": null }, "times": { - "benchmark": 352187, - "client": 396140, - "client_begin": "2024-08-05 22:43:58.107543", - "client_end": "2024-08-05 22:43:58.503683", - "http_first_byte_return": 0.39601, - "http_startup": 0.00051, + "benchmark": 315042, + "client": 320896, + "client_begin": "2024-08-05 23:08:05.027589", + "client_end": "2024-08-05 23:08:05.348485", + "http_first_byte_return": 0.320782, + "http_startup": 0.000417, "initialization": 0 } }, - "a3d417cd-4610-435b-ab1c-82f4ad1fcff0": { + "617cd20e-0327-4461-9f38-15eb7babd308": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722915837.502903", + "begin": "1722917284.367158", "cold_start_var": "", - "container_id": "3c8933e5", - "end": "1722915838.101299", - "is_cold": true, - "request_id": "a3d417cd-4610-435b-ab1c-82f4ad1fcff0", + "container_id": "d0159c55", + "end": "1722917284.689440", + "is_cold": false, + "request_id": "617cd20e-0327-4461-9f38-15eb7babd308", "result": { "measurement": { - "compute_time": 325414.0, + "compute_time": 311838.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 21113.0 + "upload_time": 10377.0 }, "output": { - "bucket": "sebs-benchmarks-0e4d1946", - "key": "120.uploader-0-output/800px-Jammlich_crop.e7782608.jpg", + "bucket": "sebs-benchmarks-6b073204", + "key": "120.uploader-0-output/800px-Jammlich_crop.a8c8716f.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -135,46 +135,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "a3d417cd-4610-435b-ab1c-82f4ad1fcff0", + "request_id": "617cd20e-0327-4461-9f38-15eb7babd308", "stats": { - "cold_start": true, + "cold_start": false, "failure": false, "memory_used": null }, "times": { - "benchmark": 598396, - "client": 816197, - "client_begin": "2024-08-05 22:43:57.289957", - "client_end": "2024-08-05 22:43:58.106154", - "http_first_byte_return": 0.815869, - "http_startup": 0.000385, + "benchmark": 322282, + "client": 334824, + "client_begin": "2024-08-05 23:08:04.357864", + "client_end": "2024-08-05 23:08:04.692688", + "http_first_byte_return": 0.334692, + "http_startup": 0.000554, "initialization": 0 } }, - "ebf954cb-a757-432d-a6ea-2c7fc501e5b5": { + "8b3c9e20-6558-4dba-b50d-369613ab3a2c": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722915838.849627", + "begin": "1722917284.695676", "cold_start_var": "", - "container_id": "3c8933e5", - "end": "1722915839.187322", + "container_id": "d0159c55", + "end": "1722917285.024566", "is_cold": false, - "request_id": "ebf954cb-a757-432d-a6ea-2c7fc501e5b5", + "request_id": "8b3c9e20-6558-4dba-b50d-369613ab3a2c", "result": { "measurement": { - "compute_time": 323360.0, + "compute_time": 315074.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 14292.0 + "upload_time": 13784.0 }, "output": { - "bucket": "sebs-benchmarks-0e4d1946", - "key": "120.uploader-0-output/800px-Jammlich_crop.7ee491ee.jpg", + "bucket": "sebs-benchmarks-6b073204", + "key": "120.uploader-0-output/800px-Jammlich_crop.625c0b7b.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -185,46 +185,46 @@ "execution": 0, "initialization": 0 }, - "request_id": "ebf954cb-a757-432d-a6ea-2c7fc501e5b5", + "request_id": "8b3c9e20-6558-4dba-b50d-369613ab3a2c", "stats": { "cold_start": false, "failure": false, "memory_used": null }, "times": { - "benchmark": 337695, - "client": 343333, - "client_begin": "2024-08-05 22:43:58.847267", - "client_end": "2024-08-05 22:43:59.190600", - "http_first_byte_return": 0.343104, - "http_startup": 0.000342, + "benchmark": 328890, + "client": 333646, + "client_begin": "2024-08-05 23:08:04.693349", + "client_end": "2024-08-05 23:08:05.026995", + "http_first_byte_return": 0.333554, + "http_startup": 0.000377, "initialization": 0 } }, - "ffd6f664-ab19-415e-bcbe-dd228427f6b3": { + "e9f0aee3-5fe9-4c5d-8d90-c5d286c88a63": { "billing": { "_billed_time": null, "_gb_seconds": 0, "_memory": null }, "output": { - "begin": "1722915838.508601", + "begin": "1722917283.352124", "cold_start_var": "", - "container_id": "3c8933e5", - "end": "1722915838.844937", - "is_cold": false, - "request_id": "ffd6f664-ab19-415e-bcbe-dd228427f6b3", + "container_id": "d0159c55", + "end": "1722917284.345936", + "is_cold": true, + "request_id": "e9f0aee3-5fe9-4c5d-8d90-c5d286c88a63", "result": { "measurement": { - "compute_time": 328533.0, + "compute_time": 372054.0, "download_size": 0, "download_time": 0, "upload_size": 235408, - "upload_time": 7756.0 + "upload_time": 14994.0 }, "output": { - "bucket": "sebs-benchmarks-0e4d1946", - "key": "120.uploader-0-output/800px-Jammlich_crop.9e6e2a56.jpg", + "bucket": "sebs-benchmarks-6b073204", + "key": "120.uploader-0-output/800px-Jammlich_crop.d84c119a.jpg", "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Jammlich_crop.jpg/800px-Jammlich_crop.jpg" } }, @@ -235,26 +235,26 @@ "execution": 0, "initialization": 0 }, - "request_id": "ffd6f664-ab19-415e-bcbe-dd228427f6b3", + "request_id": "e9f0aee3-5fe9-4c5d-8d90-c5d286c88a63", "stats": { - "cold_start": false, + "cold_start": true, "failure": false, "memory_used": null }, "times": { - "benchmark": 336336, - "client": 342241, - "client_begin": "2024-08-05 22:43:58.504423", - "client_end": "2024-08-05 22:43:58.846664", - "http_first_byte_return": 0.342163, - "http_startup": 0.000406, + "benchmark": 993812, + "client": 1237433, + "client_begin": "2024-08-05 23:08:03.112263", + "client_end": "2024-08-05 23:08:04.349696", + "http_first_byte_return": 1.237141, + "http_startup": 0.000342, "initialization": 0 } } } }, "_metrics": {}, - "begin_time": 1722915829.707452, + "begin_time": 1722917283.027722, "config": { "deployment": { "credentials": {}, @@ -262,11 +262,11 @@ "name": "fission", "removeCluster": false, "resources": { - "benchmarks": "sebs-benchmarks-0e4d1946", + "benchmarks": "sebs-benchmarks-6b073204", "docker_password": "", "docker_registry": "", "docker_username": "", - "resources_id": "0e4d1946", + "resources_id": "6b073204", "storage": { "access_key": "p1a-VpGMCOw0UX4b5LxD2zKVWklMjTVNhJxk6UiSpCg", "address": "192.168.0.128:9011", @@ -331,6 +331,6 @@ "update_storage": false } }, - "end_time": 1722915839.191321, + "end_time": 1722917285.674868, "result_bucket": null } \ No newline at end of file diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 6c81f5d5..1be0de24 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -1,5 +1,6 @@ import logging import os +import re import shutil import subprocess from typing import cast, Dict, List, Optional, Tuple, Type @@ -243,10 +244,6 @@ def package_code( # PK while creating enviroment , we need to set the env variables for minio storage_args = self.storage_arguments() - print(storage_args) - # # ['--runtime-env', 'MINIO_STORAGE_SECRET_KEY', 'a126b20a04f11ebfd7b5d68f97e0dbd59e24bcb412a2a37d0feca71d4c035164', - # '--runtime-env', 'MINIO_STORAGE_ACCESS_KEY', 'iwcJwa8y1lCAtpoM4zqdRKr8rD3lzSCA0oET07S1lco', - # '--runtime-env', 'MINIO_STORAGE_CONNECTION_URL', 'localhost:9011'] self.config.resources.create_enviroment(name = enviroment_name, image = runtime_image, builder = builder_image, runtime_env = storage_args) @@ -261,11 +258,29 @@ def package_code( function_dir = os.path.join(directory, "function") os.makedirs(function_dir) - # move all files to 'function' except handler.py + for file in os.listdir(directory): if file not in package_config: - file = os.path.join(directory, file) - shutil.move(file, function_dir) + file_path = os.path.join(directory, file) + if file == "function.py": + with open(file_path, 'r') as f: + content = f.read() + + modified_content = re.sub( + r'from \. import storage\nclient = storage\.storage\.get_instance\(\)', + 'from storage import storage\nclient = storage.get_instance()', + content + ) + + with open(os.path.join(function_dir, file), 'w') as f: + f.write(modified_content) + else: + shutil.move(file_path, function_dir) + # # move all files to 'function' except handler.py + # for file in os.listdir(directory): + # if file not in package_config: + # file = os.path.join(directory, file) + # shutil.move(file, function_dir) # FIXME: use zipfile # create zip with hidden directory but without parent directory From af2ca4708d6f18a7e142be5b3ed6f29abd590ab0 Mon Sep 17 00:00:00 2001 From: prajinkhadka Date: Fri, 9 Aug 2024 01:01:17 -0500 Subject: [PATCH 46/47] Fission Completed Testing on Python3.8 --- benchmarks/wrappers/fission/python/handler.py | 22 +-- config/example.json | 6 +- config/systems.json | 8 +- dockerfiles/fission/python/Dockerfile.build | 3 +- dockerfiles/fission/python/Dockerfile.runtime | 5 + sebs/benchmark.py | 5 - sebs/config.py | 3 + sebs/faas/config.py | 2 - sebs/fission/fission.py | 161 +++--------------- sebs/sebs.py | 5 - tools/build_docker_images.py | 8 +- 11 files changed, 47 insertions(+), 181 deletions(-) create mode 100644 dockerfiles/fission/python/Dockerfile.runtime diff --git a/benchmarks/wrappers/fission/python/handler.py b/benchmarks/wrappers/fission/python/handler.py index 92e9913a..9e4204b9 100644 --- a/benchmarks/wrappers/fission/python/handler.py +++ b/benchmarks/wrappers/fission/python/handler.py @@ -12,16 +12,11 @@ def handler(): event = request.json income_timestamp = datetime.datetime.now().timestamp() - # HTTP trigger with API Gateaway - # if 'body' in event: - # event = json.loads(event['body']) req_id = str(uuid.uuid4()) - # req_id = context.aws_request_id - # event['request-id'] = req_id event['income-timestamp'] = income_timestamp begin = datetime.datetime.now() - from function import handler - ret = handler(event) + from function import function + ret = function.handler(event) end = datetime.datetime.now() log_data = { @@ -70,16 +65,3 @@ def handler(): 'container_id': container_id, } - # return - # json.dumps({ - # 'begin': begin.strftime('%s.%f'), - # 'end': end.strftime('%s.%f'), - # 'results_time': results_time, - # 'result': log_data, - # 'is_cold': is_cold, - # 'cold_start_var': cold_start_var, - # 'container_id': container_id, - # 'statusCode': 200, - # 'request_id': req_id, - # - # }) diff --git a/config/example.json b/config/example.json index b6356700..70d97c03 100644 --- a/config/example.json +++ b/config/example.json @@ -101,9 +101,9 @@ "storage": { "address": "192.168.0.128:9011", "mapped_port": 9011, - "access_key": "p1a-VpGMCOw0UX4b5LxD2zKVWklMjTVNhJxk6UiSpCg", - "secret_key": "a40939431705f9facfe11a598fefc500d12f44d898d82709347e6802daf1b257", - "instance_id": "a83363e7e61072c5b75385357086abcb5b26df9e6baf5dc34ccbc39c2d0a98ad", + "access_key": "wfZoxJ4IJk3pO4XqPtNgPtnTGRbbQCZ-zEZjZ5SeQnQ", + "secret_key": "34f2fea2f7a66645a12c15e0c4153d74fcb5b9b3945629e7b218e3ea4d03bd9b", + "instance_id": "1f9a300aceb7af2081fdbbebbd1351d15e5bfefa2517c16fe5ee211db0f4308c", "input_buckets": [], "output_buckets": [], "type": "minio" diff --git a/config/systems.json b/config/systems.json index 6fcab935..94a764d8 100644 --- a/config/systems.json +++ b/config/systems.json @@ -244,8 +244,14 @@ "3.9":"fission/python-builder-3.9", "3.10":"fission/python-builder-3.10" }, + "base_images_runtime": { + "3.7":"fission/python-env-3.7", + "3.8":"fission/python-env-3.8", + "3.9":"fission/python-env-3.9", + "3.10":"fission/python-env-3.10" + }, "versions": ["3.7", "3.8", "3.9", "3.10"], - "images": ["build"], + "images": ["build", "runtime"], "username": "docker_user", "deployment": { "files": ["handler.py", "storage.py", "__main__.py"], diff --git a/dockerfiles/fission/python/Dockerfile.build b/dockerfiles/fission/python/Dockerfile.build index a7005779..b68eb1b4 100644 --- a/dockerfiles/fission/python/Dockerfile.build +++ b/dockerfiles/fission/python/Dockerfile.build @@ -7,7 +7,8 @@ ENV PYTHON_VERSION=${VERSION} # useradd, groupmod # RUN apt install -y shadow-utils zip # RUN apt update -y && yum install -y libffi-dev -RUN apk add --no-cache shadow zip libffi-dev gcc musl-dev make curl +RUN apk add --no-cache shadow zip libffi-dev gcc musl-dev make curl jpeg-dev zlib-dev build-base python3-dev py3-pip libtool autoconf automake git pkgconfig flex bison libxml2-dev libxslt-dev zlib-dev + # RUN apk add --no-cache shadow zip libffi-dev curl ENV GOSU_VERSION 1.14 # https://github.com/tianon/gosu/releases/tag/1.14 diff --git a/dockerfiles/fission/python/Dockerfile.runtime b/dockerfiles/fission/python/Dockerfile.runtime new file mode 100644 index 00000000..febf43bc --- /dev/null +++ b/dockerfiles/fission/python/Dockerfile.runtime @@ -0,0 +1,5 @@ +ARG BASE_IMAGE +FROM ${BASE_IMAGE} + +RUN apk add --no-cache jpeg-dev zlib-dev libxml2 + diff --git a/sebs/benchmark.py b/sebs/benchmark.py index 2804a4b5..567b21e7 100644 --- a/sebs/benchmark.py +++ b/sebs/benchmark.py @@ -342,10 +342,7 @@ def install_dependencies(self, output_dir): ).format(deployment=self._deployment_name, language=self.language_name) ) else: - print("are we in the else Block") repo_name = self._system_config.docker_repository() - print("THE REPO NAME IS", repo_name) - print("The deployment name we get here is", self._deployment_name) image_name = "build.{deployment}.{language}.{runtime}".format( deployment=self._deployment_name, language=self.language_name, @@ -353,8 +350,6 @@ def install_dependencies(self, output_dir): ) # PK: To do: Marcin Need to add this docker image with fission to the dockerhub # image_name = image_name.replace('fission', 'aws') - print("THE Iage NAME IS", image_name) - print("THE repo name ", repo_name) try: self._docker_client.images.get(repo_name + ":" + image_name) except docker.errors.ImageNotFound: diff --git a/sebs/config.py b/sebs/config.py index cfafbf00..9a247f33 100644 --- a/sebs/config.py +++ b/sebs/config.py @@ -42,6 +42,9 @@ def supported_language_versions(self, deployment_name: str, language_name: str) def benchmark_base_images(self, deployment_name: str, language_name: str) -> Dict[str, str]: return self._system_config[deployment_name]["languages"][language_name]["base_images"] + def benchmark_base_images_runtime(self, deployment_name: str, language_name: str) -> Dict[str, str]: + return self._system_config[deployment_name]["languages"][language_name]["base_images_runtime"] + def benchmark_image_name( self, system: str, diff --git a/sebs/faas/config.py b/sebs/faas/config.py index 81dc6018..8bd3e50d 100644 --- a/sebs/faas/config.py +++ b/sebs/faas/config.py @@ -210,9 +210,7 @@ def deserialize(config: dict, cache: Cache, handlers: LoggingHandlers) -> Config implementations["fission"] = FissionConfig.deserialize - print("THe implementations are", implementations) func = implementations.get(name) - print("The func is", func) assert func, "Unknown config type!" return func(config[name] if name in config else config, cache, handlers) diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index 1be0de24..d26554bc 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -21,15 +21,6 @@ from sebs.fission.storage import Minio from .function import FissionFunction, FissionFunctionConfig -# from tools.fission_preparation import ( -# check_if_minikube_installed, -# run_minikube, -# check_if_k8s_installed, -# check_if_helm_installed, -# stop_minikube, -# ) - - class Fission(System): _config: FissionConfig @@ -132,94 +123,8 @@ def build_base_image( benchmark: str, is_cached: bool, ) -> bool: + # This needs to be implemented when implementing the container deployment pass - # """ - # When building function for the first time (according to SeBS cache), - # check if Docker image is available in the registry. - # If yes, then skip building. - # If no, then continue building. - # - # For every subsequent build, we rebuild image and push it to the - # registry. These are triggered by users modifying code and enforcing - # a build. - # """ - # - # # We need to retag created images when pushing to registry other - # # than default - # registry_name = self.config.resources.docker_registry - # repository_name = self.system_config.docker_repository() - # image_tag = self.system_config.benchmark_image_tag( - # self.name(), benchmark, language_name, language_version - # ) - # if registry_name is not None and registry_name != "": - # repository_name = f"{registry_name}/{repository_name}" - # else: - # registry_name = "Docker Hub" - # - # # Check if we the image is already in the registry. - # # cached package, rebuild not enforced -> check for new one - # if is_cached: - # if self.find_image(repository_name, image_tag): - # self.logging.info( - # f"Skipping building Fission Docker package for {benchmark}, using " - # f"Docker image {repository_name}:{image_tag} from registry: " - # f"{registry_name}." - # ) - # return False - # else: - # # image doesn't exist, let's continue - # self.logging.info( - # f"Image {repository_name}:{image_tag} doesn't exist in the registry, " - # f"building Fission package for {benchmark}." - # ) - # - # build_dir = os.path.join(directory, "docker") - # os.makedirs(build_dir, exist_ok=True) - # shutil.copy( - # os.path.join(DOCKER_DIR, self.name(), language_name, "Dockerfile.function"), - # os.path.join(build_dir, "Dockerfile"), - # ) - # - # for fn in os.listdir(directory): - # if fn not in ("index.js", "__main__.py"): - # file = os.path.join(directory, fn) - # shutil.move(file, build_dir) - # - # with open(os.path.join(build_dir, ".dockerignore"), "w") as f: - # f.write("Dockerfile") - # - # builder_image = self.system_config.benchmark_base_images(self.name(), language_name)[ - # language_version - # ] - # self.logging.info(f"Build the benchmark base image {repository_name}:{image_tag}.") - # print("THE BUIDER Image is", builder_image) - # - # buildargs = {"VERSION": language_version, "BASE_IMAGE": builder_image} - # print(f"{repository_name}:{image_tag}") - # print("Build dir is", build_dir) - # print("Build Argument is", buildargs) - # image, _ = self.docker_client.images.build( - # tag=f"{repository_name}:{image_tag}", path=build_dir, buildargs=buildargs - # ) - # - # # Now push the image to the registry - # # image will be located in a private repository - # self.logging.info( - # f"Push the benchmark base image {repository_name}:{image_tag} " - # f"to registry: {registry_name}." - # ) - # - # # PK: PUshing the Image function is not implemented as of now - # - # # ret = self.docker_client.images.push( - # # repository=repository_name, tag=image_tag, stream=True, decode=True - # # ) - # # # doesn't raise an exception for some reason - # # for val in ret: - # # if "error" in val: - # # self.logging.error(f"Failed to push the image to registry {registry_name}") - # # raise RuntimeError(val) - # return True def package_code( @@ -234,59 +139,44 @@ def package_code( # Use this when wokring for container deployment supports. # self.build_base_image(directory, language_name, language_version, benchmark, is_cached) + repo_name = self.system_config.docker_repository() + enviroment_name = language_name + language_version.replace(".","") builder_image = self.system_config.benchmark_base_images(self.name(), language_name)[ language_version ] - # PK: Get this from config - builder_image = "spcleth/serverless-benchmarks:build.fission.python.3.8" - runtime_image = "fission/python-env-3.8" - # PK while creating enviroment , we need to set the env variables for minio - storage_args = self.storage_arguments() + runtime_image = "runtime.{deployment}.{language}.{runtime}".format( + deployment="fission", + language=language_name, + runtime=language_version, + ) + + runtime_image = repo_name + ":" + runtime_image + storage_args = self.storage_arguments() self.config.resources.create_enviroment(name = enviroment_name, image = runtime_image, builder = builder_image, runtime_env = storage_args) - # We deploy Minio config in code package since this depends on local - # deployment - it cannnot be a part of Docker image CONFIG_FILES = { - "python": [""], - "nodejs": ["index.js"], + "python": ["handler.py", "requirements.txt", ".python_packages"], + "nodejs": ["handler.js", "package.json", "node_modules"], } package_config = CONFIG_FILES[language_name] function_dir = os.path.join(directory, "function") os.makedirs(function_dir) - for file in os.listdir(directory): if file not in package_config: - file_path = os.path.join(directory, file) - if file == "function.py": - with open(file_path, 'r') as f: - content = f.read() - - modified_content = re.sub( - r'from \. import storage\nclient = storage\.storage\.get_instance\(\)', - 'from storage import storage\nclient = storage.get_instance()', - content - ) - - with open(os.path.join(function_dir, file), 'w') as f: - f.write(modified_content) - else: - shutil.move(file_path, function_dir) - # # move all files to 'function' except handler.py - # for file in os.listdir(directory): - # if file not in package_config: - # file = os.path.join(directory, file) - # shutil.move(file, function_dir) + file = os.path.join(directory, file) + shutil.move(file, function_dir) + # FIXME: use zipfile # create zip with hidden directory but without parent directory - execute("rm requirements.txt", shell=True, cwd=function_dir) - execute("zip -qu -r9 {}.zip * .".format(benchmark), shell=True, cwd=function_dir) - benchmark_archive = "{}.zip".format(os.path.join(function_dir, benchmark)) + execute("rm requirements.txt", shell=True, cwd=directory) + execute("zip -qu -r9 {}.zip * .".format(benchmark), shell=True, cwd=directory) + benchmark_archive = "{}.zip".format(os.path.join(directory, benchmark)) self.logging.info("Created {} archive".format(benchmark_archive)) bytes_size = os.path.getsize(os.path.join(function_dir, benchmark_archive)) @@ -299,6 +189,7 @@ def package_code( return benchmark_archive, bytes_size + def storage_arguments(self) -> List[str]: storage = cast(Minio, self.get_storage()) return [ @@ -307,18 +198,6 @@ def storage_arguments(self) -> List[str]: f"MINIO_STORAGE_CONNECTION_URL={storage.config.address}" ] - # return [ - # "--runtime-env", - # "MINIO_STORAGE_SECRET_KEY", - # storage.config.secret_key, - # "--runtime-env", - # "MINIO_STORAGE_ACCESS_KEY", - # storage.config.access_key, - # "--runtime-env", - # "MINIO_STORAGE_CONNECTION_URL", - # storage.config.address, - # ] - def create_function(self, code_package: Benchmark, func_name: str) -> "FissionFunction": package_name = func_name.replace(".", "") diff --git a/sebs/sebs.py b/sebs/sebs.py index d7d57b9b..c78f81eb 100644 --- a/sebs/sebs.py +++ b/sebs/sebs.py @@ -114,15 +114,10 @@ def get_deployment( # FIXME: future annotations, requires Python 3.7+ handlers = self.generate_logging_handlers(logging_filename) - print(deployment_config) - print(config) - print(handlers) if not deployment_config: deployment_config = Config.deserialize(config, self.cache_client, handlers) - print("Pritnign implementations of the name") - print(implementations[name]) deployment_client = implementations[name]( self._config, deployment_config, # type: ignore diff --git a/tools/build_docker_images.py b/tools/build_docker_images.py index 29f50684..3997b7f0 100755 --- a/tools/build_docker_images.py +++ b/tools/build_docker_images.py @@ -15,19 +15,19 @@ parser.add_argument("--type", default=None, choices=["build", "run", "manage"], action="store") parser.add_argument("--language", default=None, choices=["python", "nodejs"], action="store") parser.add_argument("--language-version", default=None, type=str, action="store") +parser.add_argument("--runtime", default=False, action="store_true", help="Build runtime image") args = parser.parse_args() config = json.load(open(os.path.join(PROJECT_DIR, "config", "systems.json"), "r")) client = docker.from_env() def build(image_type, system, language=None, version=None, version_name=None): - msg = "Build *{}* Dockerfile for *{}* system".format(image_type, system) if language: msg += " with language *" + language + "*" if version: msg += " with version *" + version + "*" - print(msg) + print("The message is", msg) if language is not None: dockerfile = os.path.join(DOCKER_DIR, system, language, f"Dockerfile.{image_type}") else: @@ -43,6 +43,8 @@ def build(image_type, system, language=None, version=None, version_name=None): "VERSION": version, } if version: + if image_type == "runtime": + version_name = version_name.replace("builder", "env") buildargs["BASE_IMAGE"] = version_name print( "Build img {} in {} from file {} with args {}".format( @@ -50,7 +52,6 @@ def build(image_type, system, language=None, version=None, version_name=None): ) ) try: - print("what wer are buildiong", target) client.images.build(path=PROJECT_DIR, dockerfile=dockerfile, buildargs=buildargs, tag=target) except docker.errors.BuildError as exc: print("Error! Build failed!") @@ -103,6 +104,7 @@ def build_systems(system, system_config): for system, system_dict in config.items(): if system == "general": continue + print("First if ") build_systems(system, system_dict) else: build_systems(args.deployment, config[args.deployment]) From 46a6f07eb82af7d27f888d98dc96c7130ddca4ac Mon Sep 17 00:00:00 2001 From: prajinkhadka Date: Sun, 11 Aug 2024 23:35:52 -0500 Subject: [PATCH 47/47] Fission Implementation complted. --- sebs/fission/fission.py | 1 + sebs/fission/fissionFunction.py | 40 --------- sebs/fission/minio.py | 154 -------------------------------- sebs/fission/readme.md | 43 --------- sebs/fission/triggers.py | 12 +-- tools/build_docker_images.py | 3 +- 6 files changed, 7 insertions(+), 246 deletions(-) delete mode 100644 sebs/fission/fissionFunction.py delete mode 100644 sebs/fission/minio.py delete mode 100644 sebs/fission/readme.md diff --git a/sebs/fission/fission.py b/sebs/fission/fission.py index d26554bc..efc5c1b8 100644 --- a/sebs/fission/fission.py +++ b/sebs/fission/fission.py @@ -347,6 +347,7 @@ def create_trigger(self, function: Function, trigger_type: Trigger.TriggerType) triggerName = triggerName.replace("-", "") postUrl = triggerName if trigger_type == Trigger.TriggerType.LIBRARY: + self.logging.info("Library trigger is not supported in Fission as of now.") return function.triggers(Trigger.TriggerType.LIBRARY)[0] elif trigger_type == Trigger.TriggerType.HTTP: try: diff --git a/sebs/fission/fissionFunction.py b/sebs/fission/fissionFunction.py deleted file mode 100644 index d9e0de36..00000000 --- a/sebs/fission/fissionFunction.py +++ /dev/null @@ -1,40 +0,0 @@ -# from sebs.faas.function import Function, ExecutionResult -# import json -# import datetime -# import requests -# import logging -# -# -# class FissionFunction(Function): -# def __init__(self, name: str): -# super().__init__(name) -# -# def sync_invoke(self, payload: dict): -# url = "http://localhost:5051/benchmark" -# readyPayload = json.dumps(payload) -# headers = {"content-type": "application/json"} -# begin = datetime.datetime.now() -# logging.info(f"Function {self.name} invoking...") -# response = requests.request("POST", url, data=readyPayload, headers=headers) -# end = datetime.datetime.now() -# logging.info(f"Function {self.name} returned response with code: {response.status_code}") -# fissionResult = ExecutionResult(begin, end) -# if response.status_code != 200: -# logging.error("Invocation of {} failed!".format(self.name)) -# logging.error("Input: {}".format(readyPayload)) -# -# # TODO: this part is form AWS, need to be rethink -# # self._deployment.get_invocation_error( -# # function_name=self.name, -# # start_time=int(begin.strftime("%s")) - 1, -# # end_time=int(end.strftime("%s")) + 1, -# # ) -# -# fissionResult.stats.failure = True -# return fissionResult -# returnContent = json.loads(json.loads(response.content)) -# fissionResult.parse_benchmark_output(returnContent) -# return fissionResult -# -# def async_invoke(self, payload: dict): -# raise Exception("Non-trigger invoke not supported!") diff --git a/sebs/fission/minio.py b/sebs/fission/minio.py deleted file mode 100644 index ead00c01..00000000 --- a/sebs/fission/minio.py +++ /dev/null @@ -1,154 +0,0 @@ -# #!/usr/bin/env python3 -# from sebs.faas.storage import PersistentStorage -# from typing import List, Tuple, Any -# import logging -# from time import sleep -# import minio -# import secrets -# import os -# -# -# class Minio(PersistentStorage): -# -# storage_container: Any -# input_buckets: List[str] = [] -# output_buckets: List[str] = [] -# input_index = 0 -# output_index = 0 -# access_key: str = "" -# secret_key: str = "" -# port = 9000 -# location = "fissionBenchmark" -# connection: Any -# docker_client = None -# -# def __init__(self, docker_client): -# self.docker_client = docker_client -# self.start() -# sleep(10) -# self.connection = self.get_connection() -# -# def start(self): -# self.startMinio() -# -# def startMinio(self): -# minioVersion = "minio/minio:latest" -# self.access_key = secrets.token_urlsafe(32) -# self.secret_key = secrets.token_hex(32) -# logging.info("Minio container starting") -# logging.info("ACCESS_KEY={}".format(self.access_key)) -# logging.info("SECRET_KEY={}".format(self.secret_key)) -# self.storage_container = self.docker_client.containers.run( -# minioVersion, -# command="server /data", -# ports={str(self.port): self.port}, -# environment={ -# "MINIO_ACCESS_KEY": self.access_key, -# "MINIO_SECRET_KEY": self.secret_key, -# }, -# remove=True, -# stdout=True, -# stderr=True, -# detach=True, -# ) -# self.storage_container.reload() -# networks = self.storage_container.attrs["NetworkSettings"]["Networks"] -# self.url = "{IPAddress}:{Port}".format( -# IPAddress=networks["bridge"]["IPAddress"], Port=self.port -# ) -# logging.info("Started minio instance at {}".format(self.url)) -# -# def get_connection(self): -# return minio.Minio( -# self.url, -# access_key=self.access_key, -# secret_key=self.secret_key, -# secure=False, -# ) -# -# def input(self) -> List[str]: -# return self.input_buckets -# -# def add_input_bucket(self, name: str, cache: bool = True) -> Tuple[str, int]: -# input_index = self.input_index -# bucket_name = "{}-{}-input".format(name, input_index) -# exist = self.connection.bucket_exists(bucket_name) -# try: -# if cache: -# self.input_index += 1 -# if exist: -# return (bucket_name, input_index) -# else: -# self.connection.make_bucket(bucket_name, location=self.location) -# self.input_buckets.append(bucket_name) -# return (bucket_name, input_index) -# if exist: -# return (bucket_name, input_index) -# self.connection.make_bucket(bucket_name, location=self.location) -# self.input_buckets.append(bucket_name) -# return (bucket_name, input_index) -# except ( -# minio.error.BucketAlreadyOwnedByYou, -# minio.error.BucketAlreadyExists, -# minio.error.ResponseError, -# ) as err: -# logging.error("Bucket creation failed!") -# raise err -# -# def add_output_bucket( -# self, name: str, suffix: str = "output", cache: bool = True -# ) -> Tuple[str, int]: -# output_index = self.output_index -# bucket_name = "{}-{}-{}".format(name, output_index, suffix) -# exist = self.connection.bucket_exists(bucket_name) -# try: -# if cache: -# self.output_index += 1 -# if exist: -# return (bucket_name, output_index) -# else: -# self.connection.make_bucket(bucket_name, location=self.location) -# self.output_buckets.append(bucket_name) -# return (bucket_name, output_index) -# if exist: -# return (bucket_name, output_index) -# self.connection.make_bucket(bucket_name, location=self.location) -# self.output_buckets.append(bucket_name) -# return (bucket_name, output_index) -# except ( -# minio.error.BucketAlreadyOwnedByYou, -# minio.error.BucketAlreadyExists, -# minio.error.ResponseError, -# ) as err: -# logging.error("Bucket creation failed!") -# raise err -# -# def output(self) -> List[str]: -# return self.output_buckets -# -# def download(self, bucket_name: str, key: str, filepath: str) -> None: -# objects = self.connection.list_objects_v2(bucket_name) -# objects = [obj.object_name for obj in objects] -# for obj in objects: -# self.connection.fget_object(bucket_name, obj, os.path.join(filepath, obj)) -# -# def upload(self, bucket_name: str, filepath: str, key: str): -# self.connection.put_object(bucket_name, filepath) -# -# def list_bucket(self, bucket_name: str) -> List[str]: -# buckets = [] -# for bucket in self.connection.list_buckets(): -# if bucket.name == bucket_name: -# buckets.append(bucket.name) -# return buckets -# -# def allocate_buckets(self, benchmark: str, buckets: Tuple[int, int]): -# inputNumber = buckets[0] -# outputNumber = buckets[1] -# for i in range(inputNumber): -# self.add_input_bucket(benchmark) -# for i in range(outputNumber): -# self.add_output_bucket(benchmark) -# -# def uploader_func(self, bucket_idx: int, file: str, filepath: str) -> None: -# pass diff --git a/sebs/fission/readme.md b/sebs/fission/readme.md deleted file mode 100644 index 4482d519..00000000 --- a/sebs/fission/readme.md +++ /dev/null @@ -1,43 +0,0 @@ -# Fission - -### Requirements -- docker -- kubeclt > 1.18.4 -- miniukube > 1.11.0 -- helm > 3.2.4 - -### Run -```sh -$ python sebs.py --repetitions 1 test_invoke 010.sleep ../local/tests/ test config/fission.json -``` - -### Implemented features: -- automatic Fission instalation if nessesary -- Fission environment preparation -- config for Fission -- packaging code with support requirement file (python) -- deploying function (python, node) -- adding http trigger -- cache usage implementation -- automatic sync function invoke -- measurement executions times -- if cold start checking -- storage service usage - Minio -- wrapper-handler for Python, Node -- wrapper-storage for Python -- cleanup after benchmark - -### About -Fission has very simplified [documentation][df1]. It is difficult to find detailed information that goes beyond the deploy hello world function. Many important functionalities are omitted and others have been implemented in a very non-intuitive way (limiting the display of function logs to 20 lines). *Fission* functions as a black box, making it difficult to extract information about what is happening with the function being performed. The documentation does not explain the folder structure on which the function is deployed. *Fission* does not require configuration in the form of login data because in our case it is run locally. The user does not receive any statistics regarding the performance of the function, therefore the only certain benchmarks are time measures. *Fission* works on *Kubernetes* so it is possible to access the metrics for the given application. However, these statistics will apply to the specific pod / pods and not the function itself. In addition, these are other than those required by the statistics interface from other benchmarks (*AWS*). It is possible to obtain records from *Prometheus* existing with *Fission* in the same napespace but we were unable to obtain it therefore, the values for memory benchmarks have not been implemented. *Fission* does not provide access to storage service, which is why we implemented storage based on *MinIO*. *Fission* uses *Flask* to handle requests in *Python*. - -### Package -If the function you want to place on fission consists of only one source file containing functions, you can create functions using this file directly. However, if our function requires additional resources (for example, requirements.txt, other file, other script) we must create a package in the form of a zip archive. To install the packages contained in the.txt requirements, the zip package must include a build script (for example, `build.sh`) that ensures that the package installation starts. The path to this file must be given when creating the package using the --buildcmd flag. - - -This is an example of the `build.sh` file using environment variables inside *Fission* namespace. - -```sh -pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG} -``` - -[df1]: diff --git a/sebs/fission/triggers.py b/sebs/fission/triggers.py index 4bd9f1c4..10d096c7 100644 --- a/sebs/fission/triggers.py +++ b/sebs/fission/triggers.py @@ -12,7 +12,7 @@ def __init__(self, fname: str, fission_cmd: Optional[List[str]] = None): super().__init__() self.fname = fname if fission_cmd: - self._fission_cmd = [*fission_cmd, "action", "invoke", "--result", self.fname] + self._fission_cmd = [*fission_cmd, "function", "test", "--name", self.fname] @staticmethod def trigger_type() -> "Trigger.TriggerType": @@ -21,19 +21,16 @@ def trigger_type() -> "Trigger.TriggerType": @property def fission_cmd(self) -> List[str]: assert self._fission_cmd - return self._fission_cmd @fission_cmd.setter def fission_cmd(self, fission_cmd: List[str]): - self._fission_cmd = [*fission_cmd, "action", "invoke", "--result", self.fname] + self._fission_cmd = [*fission_cmd, "function", "test", "--name", self.fname] @staticmethod def get_command(payload: dict) -> List[str]: params = [] - for key, value in payload.items(): - params.append("--param") - params.append(key) - params.append(json.dumps(value)) + params.extend(["--method", "POST", "--header", "Content-Type: application/json"]) + params.extend(["--body", json.dumps(payload)]) return params def sync_invoke(self, payload: dict) -> ExecutionResult: @@ -96,7 +93,6 @@ def trigger_type() -> Trigger.TriggerType: def sync_invoke(self, payload: dict) -> ExecutionResult: self.logging.debug(f"Invoke function {self.url}") - print("THE payload for fission here is", payload) return self._http_invoke(payload, self.url, False) def async_invoke(self, payload: dict) -> concurrent.futures.Future: diff --git a/tools/build_docker_images.py b/tools/build_docker_images.py index 3997b7f0..0fd8c981 100755 --- a/tools/build_docker_images.py +++ b/tools/build_docker_images.py @@ -16,6 +16,7 @@ parser.add_argument("--language", default=None, choices=["python", "nodejs"], action="store") parser.add_argument("--language-version", default=None, type=str, action="store") parser.add_argument("--runtime", default=False, action="store_true", help="Build runtime image") +parser.add_argument("--platform", default="linux/amd64", help="Target platform for Docker build") args = parser.parse_args() config = json.load(open(os.path.join(PROJECT_DIR, "config", "systems.json"), "r")) client = docker.from_env() @@ -52,7 +53,7 @@ def build(image_type, system, language=None, version=None, version_name=None): ) ) try: - client.images.build(path=PROJECT_DIR, dockerfile=dockerfile, buildargs=buildargs, tag=target) + client.images.build(path=PROJECT_DIR, dockerfile=dockerfile, buildargs=buildargs, tag=target, platform=args.platform) except docker.errors.BuildError as exc: print("Error! Build failed!") print(exc)