diff --git a/API/requirements.txt b/API/requirements.txt new file mode 100644 index 000000000..abcf00bb8 --- /dev/null +++ b/API/requirements.txt @@ -0,0 +1,31 @@ +attrs==23.1.0 +blinker==1.6.3 +certifi==2023.7.22 +charset-normalizer==3.3.0 +click==8.1.7 +clickclick==20.10.2 +connexion==3.0.5 +Flask==2.2.2 +flask-marshmallow==0.14.0 +Flask-SQLAlchemy==3.0.3 +greenlet==3.0.0 +idna==3.4 +inflection==0.5.1 +itsdangerous==2.1.2 +Jinja2==3.1.2 +jsonschema==4.19.1 +jsonschema-specifications==2023.7.1 +MarkupSafe==2.1.3 +marshmallow==3.20.1 +marshmallow-sqlalchemy==0.29.0 +packaging==23.2 +PyYAML==6.0.1 +referencing==0.30.2 +requests==2.31.0 +rpds-py==0.10.3 +six==1.16.0 +SQLAlchemy==2.0.22 +swagger-ui-bundle==0.0.9 +typing_extensions==4.8.0 +urllib3==2.0.6 +Werkzeug==2.2.2 diff --git a/API/setup.cfg b/API/setup.cfg new file mode 100644 index 000000000..f8f7f0e70 --- /dev/null +++ b/API/setup.cfg @@ -0,0 +1,14 @@ +[metadata] +name = bkr.api + +[options] +package_dir= + =src +packages = find: +zip_safe = False +python_requires = >= 3 +[options.packages.find] +where = src +exclude = + tests* + .gitignore diff --git a/API/setup.py b/API/setup.py new file mode 100644 index 000000000..8bf1ba938 --- /dev/null +++ b/API/setup.py @@ -0,0 +1,2 @@ +from setuptools import setup +setup() diff --git a/API/src/bkr/api/__init__.py b/API/src/bkr/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/API/src/bkr/api/arches.py b/API/src/bkr/api/arches.py new file mode 100644 index 000000000..9c4799acf --- /dev/null +++ b/API/src/bkr/api/arches.py @@ -0,0 +1,23 @@ +from flask import abort + +ARCHES = {} + +def post(arch): + if arch and arch not in ARCHES: + ARCHES[arch] = { + "arch": arch, + } + return ARCHES[arch], 201 + else: + abort( + 406, + f"{arch} already exists", + ) + +def search(offset=0, limit=None): + start = 0 + end = len(ARCHES.values()) + if offset and limit: + start = offset * limit + end = start + limit + return list(ARCHES.values())[start:end] diff --git a/API/src/bkr/api/health.py b/API/src/bkr/api/health.py new file mode 100644 index 000000000..866d7fbb3 --- /dev/null +++ b/API/src/bkr/api/health.py @@ -0,0 +1,3 @@ +def search(): + return {'msg': 'ok'}, 200 + diff --git a/API/src/bkr/api/lab_controllers.py b/API/src/bkr/api/lab_controllers.py new file mode 100644 index 000000000..f0cb14fbf --- /dev/null +++ b/API/src/bkr/api/lab_controllers.py @@ -0,0 +1,34 @@ +from flask import abort + +LABCONTROLLERS = {} + +def post(lab_controller): + fqdn = lab_controller.get("fqdn") + user_name = lab_controller.get("user_name", "") + email_address = lab_controller.get("email_address", "") + password = lab_controller.get("password", "") + + if fqdn and fqdn not in LABCONTROLLERS: + LABCONTROLLERS[fqdn] = { + "fqdn": fqdn, + "user_name": user_name, + "email_address": email_address, + "password": password, + } + return LABCONTROLLERS[fqdn], 201 + else: + abort( + 406, + f"Lab Controller with fqdn {fqdn} already exists", + ) + +def search(offset=0, limit=None): + start = 0 + end = len(LABCONTROLLERS.values()) + if offset and limit: + start = offset * limit + end = start + limit + return list(LABCONTROLLERS.values())[start:end] + +def get(lab_controller): + pass diff --git a/API/src/bkr/api/systems/__init__.py b/API/src/bkr/api/systems/__init__.py new file mode 100644 index 000000000..0bf1fd9d3 --- /dev/null +++ b/API/src/bkr/api/systems/__init__.py @@ -0,0 +1,78 @@ +from flask import abort +from bkr.api.lab_controllers import LABCONTROLLERS +from bkr.api.arches import ARCHES +#from bkr.model import System + +SYSTEMS = {} + +def post(system): + fqdn = system.get("fqdn") + owner = system.get("owner") + status = system.get("status", "unavailable") + status_reason = system.get("status_reason", "") + arches = system.get("arches", []) + power = system.get("power", {}) + location = system.get("location", "") + lender = system.get("lender", "") + vender = system.get("vender", "") + model = system.get("model", "") + serial = system.get("serial", "") + lab_controller = system.get("lab_controller", "") + + if lab_controller and lab_controller not in LABCONTROLLERS: + abort( + 406, + f"Lab Controller {lab_controller} doesn't exist", + ) + + for arch in arches: + if arch not in ARCHES: + abort( + 406, + f"{arch} doesn't exist, create it first.", + ) + + if fqdn and fqdn not in SYSTEMS: + SYSTEMS[fqdn] = { + "fqdn": fqdn, + "owner": owner, + "status": status, + "status_reason": status_reason, + "arches": arches, + "power": power, + "location": location, + "lender": lender, + "vender": vender, + "model": model, + "serial": serial, + "lab_controller": lab_controller, + } + return SYSTEMS[fqdn], 201 + else: + abort( + 406, + f"System with fqdn {fqdn} already exists", + ) + +def search(offset=0, limit=None): + start = 0 + end = len(SYSTEMS.values()) + if offset and limit: + start = offset * limit + end = start + limit + return list(SYSTEMS.values())[start:end] + +def get(fqdn): + if fqdn in SYSTEMS: + return SYSTEMS[fqdn] + else: + abort( + 404, + f"System with fqdn {fqdn} not found", + ) + +def put(fqdn): + pass + +def delete(fqdn): + pass diff --git a/API/src/bkr/api/systems/access_policies.py b/API/src/bkr/api/systems/access_policies.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/access_policies.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/activity.py b/API/src/bkr/api/systems/activity.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/activity.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/cc.py b/API/src/bkr/api/systems/cc.py new file mode 100644 index 000000000..90441970d --- /dev/null +++ b/API/src/bkr/api/systems/cc.py @@ -0,0 +1,8 @@ +def search(): + pass + +def post(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/commands.py b/API/src/bkr/api/systems/commands.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/commands.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/excluded_families.py b/API/src/bkr/api/systems/excluded_families.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/excluded_families.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/executed_tasks.py b/API/src/bkr/api/systems/executed_tasks.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/executed_tasks.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/install_options.py b/API/src/bkr/api/systems/install_options.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/install_options.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/loan.py b/API/src/bkr/api/systems/loan.py new file mode 100644 index 000000000..d0f51589d --- /dev/null +++ b/API/src/bkr/api/systems/loan.py @@ -0,0 +1,14 @@ +def search(): + pass + +def post(): + pass + +def put(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/notes.py b/API/src/bkr/api/systems/notes.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/notes.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/problem_reports.py b/API/src/bkr/api/systems/problem_reports.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/problem_reports.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/reservation.py b/API/src/bkr/api/systems/reservation.py new file mode 100644 index 000000000..d0f51589d --- /dev/null +++ b/API/src/bkr/api/systems/reservation.py @@ -0,0 +1,14 @@ +def search(): + pass + +def post(): + pass + +def put(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/status.py b/API/src/bkr/api/systems/status.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/status.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/systems/system_keys.py b/API/src/bkr/api/systems/system_keys.py new file mode 100644 index 000000000..59fcafba8 --- /dev/null +++ b/API/src/bkr/api/systems/system_keys.py @@ -0,0 +1,11 @@ +def search(): + pass + +def post(): + pass + +def get(): + pass + +def delete(): + pass diff --git a/API/src/bkr/api/templates/home.html b/API/src/bkr/api/templates/home.html new file mode 100644 index 000000000..3b9572830 --- /dev/null +++ b/API/src/bkr/api/templates/home.html @@ -0,0 +1,14 @@ + + + + + + + Flask REST API + + +

+ Hello, World! +

+ + diff --git a/API/src/bkr/app.py b/API/src/bkr/app.py new file mode 100644 index 000000000..e980f40bf --- /dev/null +++ b/API/src/bkr/app.py @@ -0,0 +1,18 @@ +import logging +import sys +import time +from connexion.resolver import RestyResolver +from bkr import config + +logger = logging.getLogger(__name__) + + +def create_app(): + app = config.connex_app + app.add_api("swagger.yml", resolver=RestyResolver('bkr.api')) + return app + + +if __name__ == "__main__": + app = create_app() + app.run(host="0.0.0.0", port=8000) diff --git a/API/src/bkr/config.py b/API/src/bkr/config.py new file mode 100644 index 000000000..27d12266b --- /dev/null +++ b/API/src/bkr/config.py @@ -0,0 +1,29 @@ +import os +import pathlib + +import sqlalchemy +import connexion +from flask_sqlalchemy import SQLAlchemy +from flask_marshmallow import Marshmallow + + +basedir = pathlib.Path(__file__).parent.resolve() +connex_app = connexion.App(__name__, specification_dir=basedir) + +app = connex_app.app +app.config.from_object("bkr.settings") +app.config.from_object(os.environ.get("BKR_SETTINGS_MODULE")) + +config = app.config +db = SQLAlchemy(app) +ma = Marshmallow(app) + + +def get_engine(db_uri): + return sqlalchemy.create_engine( + db_uri, + pool_size=app.config["SQLALCHEMY_POOL_SIZE"], + max_overflow=app.config["SQLALCHEMY_MAX_OVERFLOW"], + encoding="utf8", + ) + diff --git a/API/src/bkr/settings.py b/API/src/bkr/settings.py new file mode 100644 index 000000000..8b6827e21 --- /dev/null +++ b/API/src/bkr/settings.py @@ -0,0 +1,41 @@ +# Global parameters about the API itself +# +import os + +HOST = os.getenv("API_HOST", "127.0.0.1") +PORT = int(os.getenv("API_PORT", "5000")) +DEBUG = True +JSONIFY_PRETTYPRINT_REGULAR = False + +# Database (SQLAlchemy) related parameters +# +DB_USER = os.getenv("DB_USER", "bkr") +DB_PASSWORD = os.getenv("DB_PASSWORD", "bkr") +DB_HOST = os.getenv("DB_HOST", "127.0.0.1") +DB_PORT = int(os.getenv("DB_PORT", "5432")) +DB_NAME = os.getenv("DB_NAME", "beaker") +DEFAULT_SQLALCHEMY_DATABASE_URI = ( + "postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}".format( + db_user=DB_USER, + db_password=DB_PASSWORD, + db_host=DB_HOST, + db_port=DB_PORT, + db_name=DB_NAME, + ) +) +SQLALCHEMY_DATABASE_URI = os.getenv( + "SQLALCHEMY_DATABASE_URI", DEFAULT_SQLALCHEMY_DATABASE_URI +) + +# The following two lines will output the SQL statements +# executed by SQLAlchemy. Useful while debugging and in +# development. Turned off by default +# -------- +SQLALCHEMY_ECHO = False +SQLALCHEMY_NATIVE_UNICODE = True +SQLALCHEMY_POOL_SIZE = 5 +SQLALCHEMY_MAX_OVERFLOW = 25 + +# Logging related parameters +LOG_LEVEL = "INFO" +LOG_FORMAT = "[%(asctime)s] %(levelname)-8s %(name)-12s %(message)s" diff --git a/API/src/bkr/swagger.yml b/API/src/bkr/swagger.yml new file mode 100644 index 000000000..7b3e0fa03 --- /dev/null +++ b/API/src/bkr/swagger.yml @@ -0,0 +1,768 @@ +openapi: 3.0.0 +servers: + - description: Beaker API + url: /api +info: + version: "1.0.0" + title: Sample Application Project + description: >- + Sample Beaker API. +paths: + /health: + get: + tags: [Health] + description: Health Check + responses: + '200': + description: Status message from server describing current health + /systems: + get: + tags: + - Systems + description: >- + All the systems registered in Inventory + parameters: + - $ref: '#/components/parameters/PageLimit' + - $ref: '#/components/parameters/PageOffset' + responses: + "200": + description: "Successfully read systems list" + content: + application/json: + schema: + type: "array" + items: + $ref: "#/components/schemas/System" + application/xml: + schema: + type: "array" + xml: + name: systems + items: + $ref: "#/components/schemas/System" + post: + tags: + - Systems + description: "Create a System" + requestBody: + x-body-name: "system" + description: "System to create" + required: True + content: + application/json: + schema: + $ref: "#/components/schemas/System" + examples: + SystemExample: + $ref: '#/components/examples/System' + responses: + "201": + description: "Successfully created System" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}: + get: + tags: + - Systems + description: Obtain information about a system + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system" + '400': + $ref: '#/components/responses/400Error' + put: + tags: + - Systems + description: "Update a System" + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully updated System" + '201': + description: "Successfully created System" + '204': + description: "No changes for System" + '400': + $ref: '#/components/responses/400Error' + requestBody: + x-body-name: "system" + description: "System to update" + required: True + content: + application/json: + schema: + $ref: "#/components/schemas/System" + examples: + SystemExample: + $ref: '#/components/examples/System' + delete: + tags: + - Systems + description: "Delete a System" + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + "204": + description: "Successfully deleted System" + /systems/{fqdn}/cc: + get: + tags: + - Systems + description: Obtain information about a system CC entries + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system CC entries" + '400': + $ref: '#/components/responses/400Error' + post: + tags: + - Systems + description: "Add cc email to system" + parameters: + - $ref: '#/components/parameters/FQDN' + requestBody: + x-body-name: "email" + description: "System to create" + required: True + content: + application/json: + schema: + $ref: "#/components/schemas/EMAIL" + examples: + SystemExample: + $ref: '#/components/examples/EMAIL' + responses: + "201": + description: "Successfully created CC entry for System" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/cc/{email}: + delete: + tags: + - Systems + description: Delete a system CC entry + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/EMAIL' + responses: + '204': + description: "Successfully deleted system CC entry" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/problem-reports: + get: + tags: + - Systems + description: Obtain information about a system problem reports + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system problem reports" + '400': + $ref: '#/components/responses/400Error' + post: + tags: + - Systems + description: Add problem report to system + parameters: + - $ref: '#/components/parameters/FQDN' + requestBody: + x-body-name: "problem_report" + description: "Problem report to create" + required: True + content: + application/json: + schema: + type: "object" + required: + - message + properties: + message: + type: "string" + examples: + reportProblemExample: + value: + message: >- + This system is not powering on and is failing to netboot. + responses: + "201": + description: "Successfully created problem report entry for System" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/problem-reports/{id}: + delete: + tags: + - Systems + description: Delete a system problem report entry + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '204': + description: "Successfully deleted system problem report entry" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/problem-reports/{id}: + get: + tags: + - Systems + description: Obtain information about a system problem report + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '200': + description: "Successfully returned a system problem report" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/reservation: + put: + tags: + - Systems + description: "Reserve system" + parameters: + - $ref: '#/components/parameters/FQDN' + requestBody: + x-body-name: "reserve" + description: "Reserve a system" + required: True + content: + application/json: + schema: + $ref: "#/components/schemas/EMAIL" + examples: + SystemExample: + $ref: '#/components/examples/EMAIL' + responses: + "201": + description: "Successfully Reserved System" + '400': + $ref: '#/components/responses/400Error' + get: + tags: + - Systems + description: Obtain information about a system reservation + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system reservation" + '400': + $ref: '#/components/responses/400Error' + delete: + tags: + - Systems + description: Delete a system reservation + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '204': + description: "Successfully deleted system reservation" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/loan: + put: + tags: + - Systems + description: "A loan gives exclusive access to a system" + parameters: + - $ref: '#/components/parameters/FQDN' + requestBody: + x-body-name: "loan" + description: "Loan a system" + required: True + content: + application/json: + schema: + $ref: "#/components/schemas/EMAIL" + examples: + SystemExample: + $ref: '#/components/examples/EMAIL' + responses: + "201": + description: "Successfully Reserved System" + '400': + $ref: '#/components/responses/400Error' + get: + tags: + - Systems + description: Obtain information about a system loan + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system loan" + '400': + $ref: '#/components/responses/400Error' + delete: + tags: + - Systems + description: Delete a system loan + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '204': + description: "Successfully deleted system loan" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/access_policies: + post: + tags: + - Systems + description: Add an access policy to a system + parameters: + - $ref: '#/components/parameters/FQDN' + requestBody: + x-body-name: "access_policy" + description: "Access policy to create" + required: True + content: + application/json: + schema: + type: "object" + required: + - message + properties: + message: + type: "string" + examples: + reportProblemExample: + value: + message: >- + This system is not powering on and is failing to netboot. + responses: + "201": + description: "Successfully created access policy entry for System" + '400': + $ref: '#/components/responses/400Error' + get: + tags: + - Systems + description: Obtain information about a system access policies + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system access policies" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/access_policies/{id}: + get: + tags: + - Systems + description: Obtain information about a system access policy + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '200': + description: "Successfully returned a system access policy" + '400': + $ref: '#/components/responses/400Error' + delete: + tags: + - Systems + description: Delete a system access policy + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '204': + description: "Successfully deleted system access policy" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/status: + get: + tags: + - Systems + description: Obtain information about a system status + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system status" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/commands: + get: + tags: + - Systems + description: Obtain information about a system commands + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system commands" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/commands/{id}: + get: + tags: + - Systems + description: Obtain information about a system command + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '200': + description: "Successfully returned a system command" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/notes: + get: + tags: + - Systems + description: Obtain information about a system notes + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system notes" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/notes/{id}: + get: + tags: + - Systems + description: Obtain information about a system note + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '200': + description: "Successfully returned a system note" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/activity: + get: + tags: + - Systems + description: Obtain information about a system activity + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system activity" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/executed-tasks: + get: + tags: + - Systems + description: Obtain information about a system executed tasks + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system executed tasks" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/install-options: + get: + tags: + - Systems + description: Obtain information about a system install options + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system install options" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/install-options/{id}: + get: + tags: + - Systems + description: Obtain information about a system install option + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '200': + description: "Successfully returned a system install option" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/excluded-families: + get: + tags: + - Systems + description: Obtain information about a system excluded families + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system excluded families" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/excluded-families/{id}: + get: + tags: + - Systems + description: Obtain information about a system excluded family + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '200': + description: "Successfully returned a system excluded family" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/system-keys: + get: + tags: + - Systems + description: Obtain information about a system keys + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a system keys" + '400': + $ref: '#/components/responses/400Error' + /systems/{fqdn}/system-keys/{id}: + get: + tags: + - Systems + description: Obtain information about a system key + parameters: + - $ref: '#/components/parameters/FQDN' + - $ref: '#/components/parameters/ID' + responses: + '200': + description: "Successfully returned a system key" + /arches: + get: + tags: + - Arches + description: >- + All the arches registered in Inventory + responses: + '200': + description: "Successfully read arch list" + /lab_controllers: + get: + tags: + - Lab-Controllers + description: >- + This operation shows how to override the global security defined above, + as we want to open it up for all users. + security: [] + parameters: + - $ref: '#/components/parameters/PageLimit' + - $ref: '#/components/parameters/PageOffset' + responses: + '200': + description: "Successfully read lab controller list" + content: + application/json: + schema: + type: object + properties: + fqdn: + type: string + post: + tags: + - Lab-Controllers + description: "Create a Lab Controller" + requestBody: + x-body-name: "lab_controller" + description: "Lab Controller to create" + required: True + content: + application/json: + schema: + $ref: "#/components/schemas/LabController" + examples: + SystemExample: + $ref: '#/components/examples/LabController' + responses: + "201": + description: "Successfully created Lab Controller" + '400': + $ref: '#/components/responses/400Error' + /lab_controllers/{fqdn}: + get: + tags: + - Lab-Controllers + description: Obtain information about a lab controller + parameters: + - $ref: '#/components/parameters/FQDN' + responses: + '200': + description: "Successfully returned a lab controller" + '400': + $ref: '#/components/responses/400Error' +components: + parameters: + PageLimit: + name: limit + in: query + description: Limits the number of items on a page + schema: + type: integer + examples: + limit-example: + value: 100 + PageOffset: + name: offset + in: query + description: Specifies the page number of the items to be displayed + schema: + type: integer + examples: + offset-example: + value: 0 + FQDN: + name: "fqdn" + description: "Fully qualified domain name of system to get" + in: path + required: True + schema: + type: "string" + examples: + fqdn-example: + value: "host.example.com" + ID: + name: "id" + description: "id of the record to get" + in: path + required: True + schema: + type: "string" + examples: + id-example: + value: "1262" + EMAIL: + name: "email" + description: "Email address to include in Carbon Copy (CC)" + in: path + required: True + schema: + type: "string" + examples: + email-example: + value: "user@example.com" + schemas: + EMAIL: + type: "object" + required: + - email + properties: + email: + type: "string" + LabController: + type: "object" + required: + - fqdn + properties: + fqdn: + type: "string" + user_name: + type: "string" + email_address: + type: "string" + password: + type: "string" + removed: + type: "boolean" + disabled: + type: "boolean" + System: + type: "object" + required: + - fqdn + properties: + fqdn: + type: "string" + owner: + type: "string" + status: + type: "string" + status_reason: + type: "string" + arches: + type: "array" + items: + type: "string" + power: + type: "object" + properties: + power_type: + type: "string" + power_address: + type: "string" + power_user: + type: "string" + power_password: + type: "string" + power_id: + type: "string" + power_quiescent_period: + type: "integer" + release_action: + type: "string" + reprovision_distro_tree: + type: "string" + location: + type: "string" + lender: + type: "string" + vender: + type: "string" + model: + type: "string" + serial: + type: "string" + lab_controller: + type: "string" + responses: + 400Error: + description: Invalid request + content: + application/json: + schema: + type: object + properties: + message: + type: string + examples: + EMAIL: + value: + email: "user@example.com" + LabController: + value: + fqdn: "lab1.example.com" + user_name: "host/labctrl" + email_address: "labctrl@beaker-server.localdomain" + password: "labctrl" + removed: "false" + disabled: "false" + System: + value: + fqdn: "host.example.com" + owner: "user@fedora.com" + status: "available" + status_reason: "" + arches: + - "x86_64" + power: + power_type: "ipmi" + power_address: "127.0.0.1" + power_user: "admin" + power_password: "admin" + power_id: "6231" + power_quiescent_period: 60 + release_action: "PowerOff" + reprovision_distro_tree: "" + location: "Westford, MA" + lender: "IBM" + vender: "IBM" + model: "Z230" + serial: "A12345678" + lab_controller: "lab1.example.com" diff --git a/API/tests/conftest.py b/API/tests/conftest.py new file mode 100644 index 000000000..2eb2969f6 --- /dev/null +++ b/API/tests/conftest.py @@ -0,0 +1,14 @@ +import pytest +from bkr.app import create_app + +app = create_app() + +@pytest.fixture() +def client(): + with app.test_client() as c: + yield c + + +@pytest.fixture() +def runner(): + return app.test_cli_runner() diff --git a/API/tests/settings.py b/API/tests/settings.py new file mode 100644 index 000000000..848c0d52b --- /dev/null +++ b/API/tests/settings.py @@ -0,0 +1,3 @@ +DEBUG = False + +LOG_FILE = "/dev/null" diff --git a/API/tests/test_api.py b/API/tests/test_api.py new file mode 100644 index 000000000..aeed604e3 --- /dev/null +++ b/API/tests/test_api.py @@ -0,0 +1,4 @@ +def test_health(client): + response = client.get('/api/health') + assert response.status_code == 200 + diff --git a/API/tests/test_systems.py b/API/tests/test_systems.py new file mode 100644 index 000000000..fd9dea157 --- /dev/null +++ b/API/tests/test_systems.py @@ -0,0 +1,30 @@ +def test_lab_controller_create(client): + response = client.post("/api/lab_controllers", json={ + "fqdn": "lab1.example.com" + }) + assert response.json()["fqdn"] == "lab1.example.com" + +def test_system_create(client): + response = client.post("/api/systems", json={ + "fqdn": "host1.example.com", + "location": "Westford, MA", + "lender": "IBM", + "vender": "IBM", + "model": "Z230", + "serial": "A12345678", + "lab_controller": "lab1.example.com" + }) + assert response.json()["fqdn"] == "host1.example.com" + assert response.json()["status"] == "unavailable" + +def test_system_fail(client): + response = client.post("/api/systems", json={ + "fqdn": "host4.example.com", + "location": "Westford, MA", + "lender": "IBM", + "vender": "IBM", + "model": "Z230", + "serial": "A12345678", + "lab_controller": "nolab.example.com" + }) + assert response.status_code == 406