Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/linters/.codespellrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[codespell]
ignore-words-list = implementor
5 changes: 3 additions & 2 deletions app/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import json

import pytest
from app.glue import GlueImage, GlueInstanceType, GlueShare, GlueSite

from .glue import GlueImage, GlueInstanceType, GlueShare, GlueSite


@pytest.fixture
Expand Down Expand Up @@ -163,7 +164,7 @@ def site_info():
"InterfaceName": "org.openstack.nova",
"InterfaceVersion": "2.0",
"HealthState": "ok",
"HealthStateInfo": "Endpoint funtioning properly",
"HealthStateInfo": "Endpoint functioning properly",
"ServingState": "production",
"Technology": "webservice",
"Implementor": "OpenStack Foundation",
Expand Down
38 changes: 35 additions & 3 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Appdb Information Sistem
Appdb Information System

A simple wrapper around the cloud-info jsons to deliver the information
needed by IM
Expand All @@ -9,11 +9,13 @@
from contextlib import asynccontextmanager
from typing import Optional

from app.glue import Discipline, FileSiteStore, VOStore
from fastapi import FastAPI, HTTPException
import yaml
from fastapi import FastAPI, HTTPException, Request, Response
from pydantic import BaseModel
from pydantic_settings import BaseSettings

from .glue import Discipline, FileSiteStore, VOStore


class Image(BaseModel):
egi_id: str
Expand Down Expand Up @@ -77,6 +79,10 @@ async def lifespan(app: FastAPI):
"name": "images",
"description": "Discovery of images.",
},
{
"name": "fedcloudclient",
"description": "Fedcloudclient configuration files.",
},
]


Expand Down Expand Up @@ -222,3 +228,29 @@ def get_all_images(vo_name: str = "", only_egi_images: bool = True) -> list[Imag
else:
images.extend(Image(**img, endpoint=site.url) for img in site.image_list())
return filter_images(images, only_egi_images)


@app.get("/fedcloudclient/", tags=["fedcloudclient"])
def get_fedcloudclient_sites(request: Request) -> list[str]:
"""Get a list of available site configurations for fedcloudclient."""
return [
str(request.url_for("get_fedcloudclient_site", site_name=s.name))
for s in site_store.get_sites()
]


@app.get("/fedcloudclient/{site_name}/", tags=["fedcloudclient"])
def get_fedcloudclient_site(site_name: str) -> str:
"""Get site information as yaml compatible with fedcloudclient

Name of the site in the GOCDB
"""
site = _get_site(site_name)
fedcloud_site = {
"gocdb": site.name,
"endpoint": site.url,
"vos": [
{"name": p.vo, "auth": {"project_id": p.project_id}} for p in site.shares
],
}
return Response(content=yaml.dump(fedcloud_site), media_type="application/yaml")
Comment thread
sebastian-luna-valero marked this conversation as resolved.
43 changes: 22 additions & 21 deletions app/test_glue.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
from http import HTTPStatus
from unittest import mock

import app.glue
import httpx
import pytest

from . import glue


def test_gluesite_object(site):
site = site
Expand Down Expand Up @@ -65,8 +66,8 @@ def test_vo_store_get_vos(ops_portal):
)
)
)
vos = [app.glue.VO(**vo) for vo in ops_portal["data"]]
vo_store = app.glue.VOStore(
vos = [glue.VO(**vo) for vo in ops_portal["data"]]
vo_store = glue.VOStore(
ops_portal_url="https://example.com", httpx_client=test_client
)
assert vos == vo_store.get_vos()
Expand All @@ -78,21 +79,21 @@ def test_vo_store_get_vos_failure():
lambda request: httpx.Response(HTTPStatus.FORBIDDEN, content="foo")
)
)
vo_store = app.glue.VOStore(
vo_store = glue.VOStore(
ops_portal_url="https://example.com", httpx_client=test_client
)
assert [] == vo_store.get_vos()


def test_vo_store_get_disciplines(disciplines_json, discipline):
with mock.patch("builtins.open", mock.mock_open(read_data=disciplines_json)):
vo_store = app.glue.VOStore(vo_disciplines_file="foo.json")
assert [app.glue.Discipline(**discipline)] == vo_store.get_disciplines()
vo_store = glue.VOStore(vo_disciplines_file="foo.json")
assert [glue.Discipline(**discipline)] == vo_store.get_disciplines()


def test_vo_store_get_disciplines_bad_json():
with mock.patch("builtins.open", mock.mock_open(read_data="")):
vo_store = app.glue.VOStore(vo_disciplines_file="foo.json")
vo_store = glue.VOStore(vo_disciplines_file="foo.json")
assert [] == vo_store.get_disciplines()


Expand All @@ -103,8 +104,8 @@ def test_gocdb_info(gocdb):
)
)
with mock.patch("app.glue.SiteStore.get_mp_image_data"):
site_store = app.glue.SiteStore(
gocdb_url="https://exmaple.com", httpx_client=test_client
site_store = glue.SiteStore(
gocdb_url="https://example.com", httpx_client=test_client
)
hostname = site_store._get_gocdb_hostname("7513G0")
assert hostname == "api.cloud.ifca.es"
Expand All @@ -119,21 +120,21 @@ def test_create_site(site_info, site, images):
goc_hostname.return_value = "foo"
image_data.return_value = images[0]
m_datetime.return_value = datetime.datetime.now()
site_store = app.glue.SiteStore()
site_store = glue.SiteStore()
loaded_site = site_store.create_site(site_info)
assert site == loaded_site


def test_valid_info_check(site_info):
site_store = app.glue.SiteStore()
site_store = glue.SiteStore()
with pytest.raises(ValueError):
site_store.create_site(site_info)


def test_validity_disabled(site_info):
with mock.patch("app.glue.SiteStore._get_gocdb_hostname") as goc_hostname:
goc_hostname.return_value = "foo"
site_store = app.glue.SiteStore(check_glue_validity=False)
site_store = glue.SiteStore(check_glue_validity=False)
site = site_store.create_site(site_info)
assert site is not None

Expand All @@ -143,7 +144,7 @@ def test_get_sites(site):
mock.patch("app.glue.SiteStore.get_mp_image_data"),
mock.patch("app.glue.SiteStore._sites") as _sites,
):
site_store = app.glue.SiteStore()
site_store = glue.SiteStore()
_sites.return_value = [site]
# no VO
assert site_store.get_sites() == [site]
Expand All @@ -158,7 +159,7 @@ def test_get_site_by_goc_id(site):
mock.patch("app.glue.SiteStore.get_mp_image_data"),
mock.patch("app.glue.SiteStore._sites") as _sites,
):
site_store = app.glue.SiteStore()
site_store = glue.SiteStore()
_sites.return_value = [site]
# unknown ID
assert site_store.get_site_by_goc_id("foo") is None
Expand All @@ -171,7 +172,7 @@ def test_get_site_by_name(site):
mock.patch("app.glue.SiteStore.get_mp_image_data"),
mock.patch("app.glue.SiteStore._sites") as _sites,
):
site_store = app.glue.SiteStore()
site_store = glue.SiteStore()
_sites.return_value = [site]
# unknown name
assert site_store.get_site_by_name("foo") is None
Expand All @@ -184,7 +185,7 @@ def test_get_site_summary(site):
mock.patch("app.glue.SiteStore.get_mp_image_data"),
mock.patch("app.glue.SiteStore._sites") as _sites,
):
site_store = app.glue.SiteStore()
site_store = glue.SiteStore()
_sites.return_value = [site]
site_summary = site.summary()
# no VO
Expand All @@ -196,7 +197,7 @@ def test_get_site_summary(site):


def test_get_mp_image_data(glue_image):
site_store = app.glue.SiteStore()
site_store = glue.SiteStore()
image_info = glue_image
mp_data = site_store.get_mp_image_data(image_info)
assert mp_data == {
Expand All @@ -207,15 +208,15 @@ def test_get_mp_image_data(glue_image):


def test_load_bad_json_site_file():
site_store = app.glue.FileSiteStore()
site_store = glue.FileSiteStore()
with mock.patch("builtins.open", mock.mock_open(read_data="xxx")) as m_open:
site = site_store._load_site_file("foo")
m_open.assert_called_with("foo")
assert site is None


def test_load_json_site_file(site_info_json):
site_store = app.glue.FileSiteStore(check_glue_validity=False)
site_store = glue.FileSiteStore(check_glue_validity=False)
with mock.patch(
"builtins.open", mock.mock_open(read_data=site_info_json)
) as m_open:
Expand All @@ -225,8 +226,8 @@ def test_load_json_site_file(site_info_json):


def test_glue_site_load_duplicated(site):
site_store = app.glue.FileSiteStore(check_glue_validity=False)
duplicated = app.glue.GlueSite(**site.model_dump())
site_store = glue.FileSiteStore(check_glue_validity=False)
duplicated = glue.GlueSite(**site.model_dump())
duplicated.gocdb_id = "0G"
sites = site_store._clean_up_duplicated_sites({site.name: [duplicated, site]})
assert set([s.name for s in sites]) == set(["BIFI", "BIFI-0G"])
35 changes: 33 additions & 2 deletions app/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from unittest import mock

import pytest
from app.glue import VO, Discipline
from app.main import _get_site, app, site_store, vo_store
import yaml
from fastapi import HTTPException
from fastapi.testclient import TestClient

from .glue import VO, Discipline
from .main import _get_site, app, site_store, vo_store

client = TestClient(app)


Expand Down Expand Up @@ -175,3 +177,32 @@ def test_get_images_non_egi(site, another_site, more_images):
response = client.get("/images", params={"only_egi_images": False})
assert response.status_code == 200
assert response.json() == more_images


def test_get_fedcloud_sites(site, another_site):
with mock.patch.object(site_store, "get_sites") as m_get_sites:
m_get_sites.return_value = [site, another_site]
response = client.get("/fedcloudclient/")
assert response.status_code == 200
assert response.json() == [
"http://testserver/fedcloudclient/BIFI/",
"http://testserver/fedcloudclient/FAKE/",
]


def test_get_fedcloud_site(site):
with mock.patch.object(site_store, "get_site_by_name") as m_get_site:
m_get_site.return_value = site
response = client.get("/fedcloudclient/foo")
assert response.status_code == 200
expected_site = {
"endpoint": "https://colossus.cesar.unizar.es:5000/v3",
"gocdb": "BIFI",
"vos": [
{
"name": "ops",
"auth": {"project_id": "038db3eeca5c4960a443a89b92373cd2"},
}
],
}
assert yaml.safe_load(response.text) == expected_site