Skip to content

Commit

Permalink
boa-remote-computing: impl, interfaces, tests
Browse files Browse the repository at this point in the history
  • Loading branch information
felliott committed Dec 2, 2024
1 parent b83e1fa commit a5585d3
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 0 deletions.
2 changes: 2 additions & 0 deletions addon_imps/remote_computing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""addon_imps.remote_computing: imps that implement a "remote_computing"-like interface
"""
34 changes: 34 additions & 0 deletions addon_imps/remote_computing/boa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging

from boaapi.boa_client import (
BOA_API_ENDPOINT,
BoaClient,
BoaException,
)
from django.core.exceptions import ValidationError

from addon_toolkit.interfaces import remote_computing


logger = logging.getLogger(__name__)


class BoaRemoteComputingImp(remote_computing.RemoteComputingAddonClientRequestorImp):
"""sending compute jobs to Iowa State's Boa cluster."""

@classmethod
def confirm_credentials(cls, credentials):
try:
boa_client = cls.create_client(credentials)
boa_client.close()
except BoaException:
raise ValidationError(
"Fail to validate username and password for "
"endpoint:({BOA_API_ENDPOINT})"
)

@staticmethod
def create_client(credentials):
boa_client = BoaClient(endpoint=BOA_API_ENDPOINT)
boa_client.login(credentials.username, credentials.password)
return boa_client
Empty file.
60 changes: 60 additions & 0 deletions addon_imps/tests/remote_computing/test_boa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logging
import unittest
from unittest.mock import (
MagicMock,
patch,
)

from boaapi.boa_client import (
BOA_API_ENDPOINT,
BoaException,
)
from django.core.exceptions import ValidationError

from addon_imps.remote_computing.boa import BoaRemoteComputingImp
from addon_toolkit.credentials import UsernamePasswordCredentials
from addon_toolkit.interfaces.remote_computing import RemoteComputingConfig


logger = logging.getLogger(__name__)


class TestBoaRemoteComputingImp(unittest.IsolatedAsyncioTestCase):

@patch.object(BoaRemoteComputingImp, "create_client")
def setUp(self, create_client_mock):
self.base_url = BOA_API_ENDPOINT
self.config = RemoteComputingConfig(external_api_url=self.base_url)
self.client = MagicMock()
self.credentials = UsernamePasswordCredentials(username="dog", password="woof")
self.imp = BoaRemoteComputingImp(
config=self.config, credentials=self.credentials
)
self.imp.client = self.client

@patch.object(BoaRemoteComputingImp, "create_client")
def test_confirm_credentials_success(self, create_client_mock):
creds = UsernamePasswordCredentials(username="dog", password="woof")
self.imp.confirm_credentials(creds)

create_client_mock.assert_called_once_with(creds)
create_client_mock.return_value.close.assert_called_once_with()

@patch.object(
BoaRemoteComputingImp, "create_client", side_effect=BoaException("nope")
)
def test_confirm_credentials_fail(self, create_client_mock):
creds = UsernamePasswordCredentials(username="dog", password="woof")
create_client_mock.return_value.side_effect = BoaException("could not login")
with self.assertRaises(ValidationError):
self.imp.confirm_credentials(creds)

create_client_mock.assert_called_once_with(creds)

@patch(f"{BoaRemoteComputingImp.__module__}.BoaClient")
def test_create_client(self, create_mock):
mock_login = MagicMock()
create_mock.login.return_value = mock_login

create_mock.assert_called_once_with(endpoint=BOA_API_ENDPOINT)
mock_login.assert_called_once_with(username="dog", password="woof")
6 changes: 6 additions & 0 deletions addon_service/common/known_imps.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
mendeley,
zotero_org,
)
from addon_imps.remote_computing import boa
from addon_imps.storage import (
bitbucket,
box_dot_com,
Expand Down Expand Up @@ -83,6 +84,8 @@ class KnownAddonImps(enum.Enum):
GITLAB = gitlab.GitlabStorageImp
DROPBOX = dropbox.DropboxStorageImp

BOA = boa.BoaRemoteComputingImp

if __debug__:
BLARG = my_blarg.MyBlargStorage

Expand All @@ -106,5 +109,8 @@ class AddonImpNumbers(enum.Enum):
BITBUCKET = 1012

GIT_HUB = 1013

BOA = 1020

if __debug__:
BLARG = -7
3 changes: 3 additions & 0 deletions addon_toolkit/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from . import (
citation,
remote_computing,
storage,
)
from ._base import BaseAddonInterface
Expand All @@ -12,9 +13,11 @@
"BaseAddonInterface",
"storage",
"citation",
"remote_computing",
)


class AllAddonInterfaces(enum.Enum):
STORAGE = storage.StorageAddonInterface
CITATION = citation.CitationServiceInterface
REMOTE_COMPUTING = remote_computing.RemoteComputingAddonInterface
67 changes: 67 additions & 0 deletions addon_toolkit/interfaces/remote_computing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""a static (and still in progress) definition of what composes a remote computing addon"""

import dataclasses
import typing

from addon_toolkit.constrained_network.http import HttpRequestor
from addon_toolkit.credentials import Credentials
from addon_toolkit.imp import AddonImp

from ._base import BaseAddonInterface


__all__ = (
"RemoteComputingAddonInterface",
"RemoteComputingAddonImp",
"RemoteComputingConfig",
)


###
# dataclasses used for operation args and return values


@dataclasses.dataclass(frozen=True)
class RemoteComputingConfig:
external_api_url: str
external_account_id: str | None = None


###
# declaration of all remote computing addon operations


class RemoteComputingAddonInterface(BaseAddonInterface, typing.Protocol):

pass


@dataclasses.dataclass
class RemoteComputingAddonImp(AddonImp):
"""base class for remote computing addon implementations"""

ADDON_INTERFACE = RemoteComputingAddonInterface

config: RemoteComputingConfig


@dataclasses.dataclass
class RemoteComputingAddonHttpRequestorImp(RemoteComputingAddonImp):
"""base class for remote computing addon implementations using GV network"""

network: HttpRequestor


@dataclasses.dataclass
class RemoteComputingAddonClientRequestorImp[T](RemoteComputingAddonImp):
"""base class for remote computing addon with custom clients"""

client: T = dataclasses.field(init=False)
credentials: dataclasses.InitVar[Credentials]

def __post_init__(self, credentials):
self.client = self.create_client(credentials)

@staticmethod
def create_client(credentials) -> T:
raise NotImplementedError

0 comments on commit a5585d3

Please sign in to comment.