Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] Add API server and SysML v2 Kernel #29

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
32a7412
Started to add the capability to install the SysML kernel (working) b…
Aug 16, 2021
3075687
Merge branch 'dev' into add-api-server
Aug 16, 2021
5ed5981
Merged in dev
Aug 16, 2021
cb5df1c
Merged in dev and updated envs
Aug 16, 2021
606d12e
Linted and fixed some small things
Aug 16, 2021
4f28f9f
Tweaking that commands for the API
Aug 22, 2021
db9f02a
Tweaked how the _run method works
Sep 6, 2021
4fa4bbf
Merge remote-tracking branch 'origin/dev' into add-api-server
Sep 6, 2021
c06b6db
Merge pull request #26 from sanbales/main
bjorncole Sep 9, 2021
a710d15
Fixes for connector M0, working circuit example
bjorncole Sep 11, 2021
1f70991
Started to add OpenMDAO functionality to the Circuit Permutations nb
Sep 11, 2021
28e5675
Refactored how the file loading works and fix some issues with saving
Sep 11, 2021
07b5705
Added Circuit Builder fixture
Sep 11, 2021
e578550
Refactored and cleaned up random_generator_playbook_phase_5 function
Sep 11, 2021
55da039
Tweaked the file loading a bit
Sep 11, 2021
4447310
Refactored how the API client works, and started cleaning up the conf…
Sep 11, 2021
e1a58ee
Fixed bug in the ContainmentTree
Sep 11, 2021
ecc3ed9
Fixes after the dynamic loading upgrade
Sep 11, 2021
78d4c29
Added ability to download elements from API and some tests to cover n…
Sep 12, 2021
09e7e2e
Minor tweaks to the model tests
Sep 12, 2021
9140809
Updated the skip comments for tests that need fixing
Sep 12, 2021
b48e08a
Renamed SysML2FileLoader to FileLoader
Sep 12, 2021
39d5c50
Updated the animated Gif in the README
sanbales Sep 12, 2021
8020d2d
Added check for empty ChainingFeatures in random_generator_playbook_p…
sanbales Sep 12, 2021
1c74e1d
DOCSTRING and other minor fixes to interp_playbooks
sanbales Sep 12, 2021
64f90d1
Added a fixed Circuit Builder model and fixed an issue when saving im…
sanbales Sep 13, 2021
79cc493
Updated the circuit builder fixture model and Tutorial and Circuit Pe…
sanbales Sep 13, 2021
ba3dcec
Fixed minor issue with the tutorial notebook
sanbales Sep 13, 2021
d0019f8
Minor tweaks to the interp_playbooks
sanbales Sep 13, 2021
f513c4f
Changed the icon for the Function metatype
sanbales Sep 13, 2021
2af0f67
Added updates to the API client widget load bar, added a fix to handl…
sanbales Sep 13, 2021
d5ab217
Linted the client
sanbales Sep 13, 2021
9488c17
Tweaked how the progress bar works, and added more data for elements …
Sep 14, 2021
c7c0e86
Updated the Circuit Builder model
Sep 14, 2021
ee3694d
Minor refactor of _derived Element properties and .label
Sep 14, 2021
14e7e8b
Cleaned up the Basic tutorial nb
Sep 20, 2021
0a0ff0d
Updating the Circuit Permutations nb as per buddy coding session from…
Sep 20, 2021
94416cb
Added openmdao files to gitignore
Sep 20, 2021
c30bb2c
Tweaking how the FileLoader removes and updates observers
Sep 20, 2021
7da2ad0
Fixes to the connector maker in inter playbooks
Sep 20, 2021
36fe48f
Trying to clean up Circuit Permutations nb
Sep 20, 2021
cca6ab3
black doing its thing
Sep 20, 2021
2b91f71
Added a notebook to more intelligently plot the circuit graph.
bjorncole Sep 21, 2021
44f4360
Merge remote-tracking branch 'bjorn/main' into dev
Sep 21, 2021
2e37efc
Improved circuit visualization in Circuit Permutations notebook
Sep 22, 2021
bf865f1
Tweaked the circuit plotter in Circuit Permutations nb
Sep 22, 2021
7ead20c
Added node to characterize generated circuit topology
bjorncole Sep 22, 2021
fc80af6
Update Circuit Permutations.ipynb
bjorncole Sep 23, 2021
eea5012
Updated the Circuit Permutations nb so that it can at least run the e…
Sep 23, 2021
1b02c00
Got the Circuit Permutations nb cleaned up a bit
Sep 23, 2021
a955a9a
Identified independent loop edges for circuit processing
bjorncole Sep 24, 2021
fa76c80
Adding a Circuit Example generator to tests
Sep 25, 2021
661840a
Made the Random Playbook Generator work with LPG or Model
Sep 25, 2021
fe08703
Linted the tests
Sep 25, 2021
668b974
Updated the Circuit Permutations notebook to include the new UI
Sep 25, 2021
1171e76
Got the circuit example to run the parametrics for a given model
Sep 27, 2021
9fbafd9
Updated the circuit example model and the notebooks
Sep 28, 2021
17760f5
Cleaned up the notebooks so they at least run almost all the way through
Jan 30, 2022
86b7afb
Merge remote-tracking branch 'origin/dev' into add-api-server
Jan 30, 2022
8a46e53
Minor tweak to scripts
Apr 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ ipython_config.py
# OpenMDAO
cases.sql
n2.html
connections.html
openmdao_checks.out

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
Expand Down Expand Up @@ -156,3 +158,4 @@ envs/
api/
db/
downloads/
raw.json
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ Copy the URL where JupyterLab is running into your preferred browser, and you sh
## Widgets

You can interact with the SysML v2 data using widgets, as illustrated below:
![Composed Widget](https://user-images.githubusercontent.com/1438114/113528145-bb494280-958d-11eb-8d9f-5b8f7d2b1dbe.gif)
![Composed Widget](https://user-images.githubusercontent.com/1438114/132993186-858063a7-1bb7-483b-b3be-0d61d3d27fca.gif)

> If you can't see the animation clearly, click on it to see it in higher resolution.

## Setup SysML Kernel

If you want to experiment with the SysML v2 kernel, you can install it on the environment you want by running the following command:

```bash
anaconda-project run sysml:kernel:install
```

## Setup SysML API Kernel

You can setup and run a local SysML Pilot Implementation API server by running:

```bash
anaconda-project run sysml:api:setup # to install the service
anaconda-project run sysml:api:start # to start the server
anaconda-project run sysml:api:stop # to stop the server
```
Empty file added _scripts/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions _scripts/_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import sys
from pathlib import Path
from shutil import which

ROOT = (Path(__file__) / "../..").resolve()

DOWNLOADS = ROOT / "downloads"

ENV_ROOT = Path(sys.executable).parent

DOT_LOCATION = which("dot")
if DOT_LOCATION:
DOT_LOCATION = Path(DOT_LOCATION)

POSTGRES_USER = "postgres"
POSTGRES_PASSWORD = "mysecretpassword"
POSTGRES_DB = "sysml2"

PSQL_DATA = ROOT / "db"
PSQL_LOGS = PSQL_DATA / "logs"
PSQL_PWFILE = ROOT / "_pwfile"

API = ROOT / "api"
SBT_BIN = API / "sbt/bin"
28 changes: 28 additions & 0 deletions _scripts/_variables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import platform
from os import environ as ENV_VARS

OK = 0
ERR = 1

# system
PLATFORM = platform.system()
WIN = PLATFORM == "Windows"
OSX = PLATFORM == "Darwin"
CI = ENV_VARS.get("JENKINS_HOME")

# installer versions
SBT_VERSION = ENV_VARS.get("SBT_VERSION", "1.2.8")
SYSML2_API_RELEASE = ENV_VARS.get("SYSML2_API_RELEASE", "2021-06")
SYSML2_RELEASE = ENV_VARS.get("SYSML2_RELEASE", "2021-06.1")

# API Configuration
API_PORT = ENV_VARS.get("API_PORT", 9000)
LOCAL_API_SERVER_URL = ENV_VARS.get("LOCAL_API_SERVER_URL", "http://localhost")
REMOTE_API_SERVER_URL = ENV_VARS.get("REMOTE_API_SERVER_URL", "http://sysml2.intercax.com")
SBT_GITHUB = "https://github.com/sbt/sbt"
SYSML_RELEASE_GITHUB = "https://github.com/Systems-Modeling/SysML-v2-Release"
SYSML_API_GITHUB = "https://github.com/Systems-Modeling/SysML-v2-API-Services"

PSQL_DBNAME = ENV_VARS.get("PSQL_DBNAME", "sysml2")
PSQL_USERNAME = ENV_VARS.get("PSQL_USERNAME", "postgres")
PSQL_PASSWORD = ENV_VARS.get("PSQL_PASSWORD", "pUtY0uR$eCr3tP@$sW0rDh3R3")
282 changes: 282 additions & 0 deletions _scripts/api_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
import re
import sys
import traceback
from argparse import ArgumentParser
from dataclasses import dataclass
from pathlib import Path
from zipfile import ZipFile

from . import _paths as P
from . import _variables as V
from .utils import COLOR as C
from .utils import _check_output, _run, download_file

API_ZIP_FILE = P.DOWNLOADS / "api_server.zip"
API_DOWNLOAD_URL = f"{V.SYSML_API_GITHUB}/archive/refs/tags/{V.SYSML2_API_RELEASE}.zip"

SBT_ZIP_FILE = P.DOWNLOADS / "sbt.zip"
SBT_DOWNLOAD_URL = f"{V.SBT_GITHUB}/releases/download/v{V.SBT_VERSION}/sbt-{V.SBT_VERSION}.zip"

# Executables
PG_CTL = "pg_ctl"
PSQL = "psql"
SBT = P.SBT_BIN / ("sbt" + (".bat" if V.WIN else ""))

PARSER = ArgumentParser()

PARSER.add_argument(
"--host",
default="127.0.0.1",
type=str,
help="Hostname on which to run PosgreSQL (default='127.0.0.1')",
)
PARSER.add_argument(
"--port",
default=5432,
type=int,
help="Port on which to run PosgreSQL (default=5432)",
)
PARSER.add_argument("--setup", action="store_true")
PARSER.add_argument("--silent", action="store_true")
PARSER.add_argument("--start", action="store_true")
PARSER.add_argument("--stop", action="store_true")
PARSER.add_argument("--tear-down", action="store_true")
PARSER.add_argument("--no-autostart", action="store_true")


# Commands:
# 1. Create User `postgres`
# 2. Create database 'sysml2'
# 3.


def setup_api(force=False, skip_clean=True):
"""Setup the SysML v2 Pilot API and the Scala Build Tools (SBT)"""
if not P.API.exists() or not list(P.API.iterdir()):
if force or not API_ZIP_FILE.exists():
if (
download_file(
url=API_DOWNLOAD_URL,
filename=API_ZIP_FILE,
)
!= 0
):
print(
f"{C.FAIL} Failed to download the SysML v2 Pilot API!"
f" (url={API_DOWNLOAD_URL} {C.ENDC}"
)
# unzip the contents and remove the zip file
try:
with ZipFile(API_ZIP_FILE) as zip_file:
zip_file.extractall(path=P.API)
if not skip_clean:
API_ZIP_FILE.unlink()
except: # pylint: disable=bare-except; # noqa: 722
print(f"{C.FAIL} Could not unzip file '{API_ZIP_FILE}' to '{P.API}' {C.ENDC}")
print(f"{C.FAIL} {traceback.format_exc()} {C.ENDC}")

if not P.SBT_BIN.exists():
# download and extract file
if force or not SBT_ZIP_FILE.exists():
if (
download_file(
url=SBT_DOWNLOAD_URL,
filename=SBT_ZIP_FILE,
)
!= 0
):
print(f"{C.FAIL} Failed to download SBT! (url={SBT_DOWNLOAD_URL} {C.ENDC}")
# unzip the contents and remove the zip file
try:
with ZipFile(SBT_ZIP_FILE) as zip_file:
zip_file.extractall(path=P.API)
if not skip_clean:
SBT_ZIP_FILE.unlink()
except: # pylint: disable=bare-except; # noqa: 722
print(f"{C.FAIL} Could not unzip file '{SBT_ZIP_FILE}' to '{P.API}' {C.ENDC}")
print(f"{C.FAIL} {traceback.format_exc()} {C.ENDC}")


def tear_down():
try:
P.PSQL_DATA.unlink(missing_ok=True)
except: # pylint: disable=bare-except; # noqa: 722
print(f"{C.FAIL} Failed to delete the PostgreSQL data folder {C.ENDC}")
print(f"{C.FAIL} {traceback.format_exc()} {C.ENDC}")
try:
P.API.unlink(missing_ok=True)
except: # pylint: disable=bare-except; # noqa: 722
print(f"{C.FAIL} Failed to delete the API folder {C.ENDC}")
print(f"{C.FAIL} {traceback.format_exc()} {C.ENDC}")


@dataclass
class PostgreSQLProcess: # pylint: disable=too-many-instance-attributes
"""
A context manager for a psql process.

IMPORTANT: This configuration is not intended to be used in production!

"""

pid_regex = re.compile(r"\(PID: ?(\d+)\)")

host: str = "127.0.0.1"
port: int = 5432
autostart: bool = True
silent: bool = False

_pid: int = None

_dbname: str = V.PSQL_DBNAME
_datafile: Path = P.PSQL_DATA.resolve().absolute()
_logsfile: Path = P.PSQL_LOGS.resolve().absolute()
_pwfile: Path = P.PSQL_PWFILE.resolve().absolute()
_username: str = V.PSQL_USERNAME

def __post_init__(self):
if "0.0.0.0" in self.host:
print(
f"{C.WARNING} You are binding to PostgreSQL to 0.0.0.0, "
"this exposes serious risks! {C.ENDC}"
)

if not self._datafile.exists() or not list(self._datafile.iterdir()):
self.initialize_db()

if not self._logsfile.exists():
self._logsfile.parent.mkdir(parents=True, exist_ok=True)

if self.autostart:
self.start_proc()

def clear(self):
self.server("stop")
self._datafile.unlink()

def write_pwfile(self):
"""
Write password to pwfile

IMPORTANT: This is NOT secure!!! It should NOT be used in production!
"""
if self._pwfile.exists():
self._pwfile.unlink()
self._pwfile.write_text(f"{V.PSQL_PASSWORD}\n")

def initialize_db(self):
P.PSQL_DATA.mkdir(parents=True, exist_ok=True)

# Initialize the database
silent_args = ["--silent"] if self.silent else []
result_code = _run(
[
PG_CTL,
"initdb",
f"--pgdata={self._datafile}",
f"--pwfile={self._pwfile}",
f"--username={self._username}",
]
+ silent_args,
cwd=P.ROOT,
wait=True,
)
if result_code != 0:
print(f"{C.FAIL} Database initialization (initdb) returned a non-zero value {C.ENDC}")

def create_db(self):
# Create the sysml database
is_running = self.is_running()
if not is_running:
self.server("start")

result_code = _run(
[
"createdb",
f"--owner={self._username}",
f"--host={self.host}",
f"--port={self.port}",
"--no-password",
self._dbname,
],
cwd=self._datafile,
wait=True,
)

if result_code != 0:
print(f"{C.FAIL} Database creation (createdb) returned a non-zero value {C.ENDC}")
if not is_running:
self.server("stop")

def server(self, command: str, wait=False):
func = _run if wait else _check_output
print(f"{C.OKBLUE} running '{command}' server command {C.ENDC}")

os_dep_args = ["-U", V.PSQL_USERNAME] if V.WIN else []
return func(
[
PG_CTL,
f"--pgdata={self._datafile}",
f"--log={self._logsfile}",
command,
]
+ os_dep_args,
cwd=P.ROOT,
wait=wait,
)

@property
def server_status(self):
return self.server("status", wait=True) # .decode()

def start_proc(self):
if self.is_running():
print(f"{C.WARNING} PostgreSQL is already running {C.ENDC}")
return

self.server("start")

if not self.is_running():
print(f"{C.FAIL} Could not start PostgreSQL {C.ENDC}")
else:
print(f"{C.OKGREEN} Serving PostgreSQL on {self.host}:{self.port} {C.ENDC}")

def is_running(self):
pids = self.pid_regex.findall(self.server_status)
self._pid = pid = int(pids[0]) if pids else None
return pid

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, exc_traceback):
print(f"{C.OKBLUE} Shutting down psql on {self.host}:{self.port} {C.ENDC}")
self.server("stop")
print(f"{C.OKGREEN} Finished shutting down psql on {self.host}:{self.port} {C.ENDC}")


def setup():
pass


if __name__ == "__main__":
args, extra_args = PARSER.parse_known_args(sys.argv[1:])

result_codes = []
if args.tear_down:
result_codes.append(tear_down())

if args.setup:
result_codes.append(setup())

with PostgreSQLProcess(
host=args.host,
port=args.port,
autostart=not args.no_autostart,
) as psql_proc:
if args.start:
result_codes.append(
# launch(env=env, port=args.mbee_port)
)

sys.exit(max(result_codes))
Loading