Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
ziiiki committed Feb 9, 2024
2 parents f2254d1 + 3a4134c commit b203074
Show file tree
Hide file tree
Showing 17 changed files with 168 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
timeout-minutes: 10

env:
QIBOCONNECTION_ENVIRONMENT: "development"
QUANTUM_SERVICE_URL : "https://dev-api.qaas.qilimanjaro.tech"

steps:
- uses: actions/checkout@v2
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

### Features

- job name and summary
[#145](https://github.com/qilimanjaro-tech/qiboconnection/pull/145)

- Job cancellation
[#142](https://github.com/qilimanjaro-tech/qiboconnection/pull/142)

- Add environment variables QUANTUM_SERVICE_URL and AUDIENCE_URL for a multi-client public API
[#136](https://github.com/qilimanjaro-tech/qiboconnection/pull/136/files)

## 0.15.3

### Features

- Introduction of QProgram [#139](https://github.com/qilimanjaro-tech/qiboconnection/pull/139)

## 0.16.0

### Features

- Job cancellation
[#142](https://github.com/qilimanjaro-tech/qiboconnection/pull/142)

Expand Down
1 change: 1 addition & 0 deletions src/qiboconnection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
~api.API
"""


__version__ = "0.16.0"


Expand Down
6 changes: 5 additions & 1 deletion src/qiboconnection/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ def execute(
qprogram: dict | None = None,
nshots: int = 10,
device_ids: List[int] | None = None,
name: str = "-",
summary: str = "-",
) -> List[int]:
"""Send a Qibo circuit(s) to be executed on the remote service API. User should define either a *circuit* or an
*experiment*. If both are provided, the function will fail.
Expand Down Expand Up @@ -356,6 +358,8 @@ def execute(
circuit=circuit,
qprogram=qprogram,
nshots=nshots,
name=name,
summary=summary,
user=self._connection.user,
device=cast(Device, device),
)
Expand Down Expand Up @@ -775,5 +779,5 @@ def cancel_job(self, job_id: int) -> None:
data={"job_id": job_id}, path=f"{self._JOBS_CALL_PATH}/cancel/{job_id}"
)
if status_code != 204:
raise RemoteExecutionException(message="Job could not be cancelled.", status_code=status_code)
raise RemoteExecutionException(message=f"Job {job_id} could not be cancelled.", status_code=status_code)
logger.info(f"Job {job_id} cancelled successfully")
4 changes: 4 additions & 0 deletions src/qiboconnection/models/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class Job(ABC): # pylint: disable=too-many-instance-attributes
nshots: int = 10
job_status: JobStatus = JobStatus.NOT_SENT
job_result: JobResult | None = None
name: str = "-"
summary: str = "-"
id: int = 0 # pylint: disable=invalid-name

def __post_init__(self):
Expand Down Expand Up @@ -94,6 +96,8 @@ def job_request(self) -> JobRequest:
device_id=self.device.id,
number_shots=self.nshots,
job_type=self.job_type,
name=self.name,
summary=self.summary,
description=self._get_job_description(),
)

Expand Down
1 change: 1 addition & 0 deletions src/qiboconnection/typings/enums/job_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ class JobStatus(StrEnum):
COMPLETED = "completed"
ERROR = "error"
QUEUED = "queued"
CANCELLED = "cancelled"
2 changes: 2 additions & 0 deletions src/qiboconnection/typings/requests/job_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ class JobRequest(ABC):
number_shots: int
job_type: str
description: str
name: str
summary: str
2 changes: 2 additions & 0 deletions src/qiboconnection/typings/responses/job_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class JobResponse(JobRequest):
queue_position: int
result: str
status: str
name: str
summary: str

@classmethod
def from_kwargs(cls, **kwargs):
Expand Down
5 changes: 4 additions & 1 deletion tests/end2end/tests/test_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ def test_one_qubit_500_gates_circuit(device: Device, api: API, one_qubits_500_ga
"""
logger.info(f"Device: {device}")
check_operation_possible_or_skip(Operation.RESPONSE, device=device)
jobdata = post_and_get_result(api=api, device=device, circuit=one_qubits_500_gates_circuit)
jobdata = post_and_get_result(
api=api, device=device, circuit=one_qubits_500_gates_circuit, name="test", summary="test"
)
result = jobdata.result
assert isinstance(result, list)
assert jobdata.name == jobdata.summary == "test"

delete_job(api, job_id=jobdata.job_id)

Expand Down
87 changes: 87 additions & 0 deletions tests/end2end/tests/test_user_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from qiboconnection.models.devices import Device
from qiboconnection.models.runcard import Runcard
from qiboconnection.typings.enums import DeviceAvailability, DeviceStatus
from qiboconnection.typings.enums.job_status import JobStatus
from qiboconnection.typings.job_data import JobData
from tests.end2end.utils.operations import Operation, check_operation_possible_or_skip
from tests.end2end.utils.utils import (
Expand Down Expand Up @@ -304,6 +305,92 @@ def test_post_get_and_delete_job(user_role: UserRole, numpy_circuit: Circuit, de
api.delete_job(job_id=job_id)


# ------------------------------------------------------------------------ OPERATION: POST AND CANCEL JOB


@pytest.mark.parametrize(
"device, user_role",
[(device, user_role) for user_role in list_user_roles() for device in get_devices_listing_params(user_role)],
)
@pytest.mark.slow
def test_post_cancel_and_get_job(user_role: UserRole, numpy_circuit: Circuit, device: Device):
"""Post a circuit and cancel it.
Args:
user_role (UserRole): _description_
numpy_circuit (Circuit): _description_
device (Device): _description_
"""
check_operation_possible_or_skip(Operation.CANCEL, device=device)
api_user = get_api_or_fail_test(get_logging_conf_or_fail_test(user_role=user_role))

api_user.select_device_id(device_id=device.id)
job_id = api_user.execute(circuit=numpy_circuit)[0]
api_user.cancel_job(job_id)
match device._status:
case DeviceStatus.OFFLINE:
assert api_user.get_job(job_id).status == JobStatus.PENDING
case _:
assert api_user.get_job(job_id).status == JobStatus.CANCELLED


@pytest.mark.parametrize(
"device, user_role",
[(device, user_role) for user_role in list_user_roles() for device in get_devices_listing_params(user_role)],
)
@pytest.mark.slow
def test_admin_can_cancel_jobs_from_all_users(user_role: UserRole, numpy_circuit: Circuit, device: Device, api: API):
"""Post a circuit with all user roles and cancel it with admin user.
Args:
user_role (UserRole): _description_
numpy_circuit (Circuit): _description_
device (Device): _description_
"""
check_operation_possible_or_skip(Operation.CANCEL, device=device)
api_user = get_api_or_fail_test(get_logging_conf_or_fail_test(user_role=user_role))

api_user.select_device_id(device_id=device.id)
job_id = api_user.execute(circuit=numpy_circuit)[0]
api.cancel_job(job_id)
match device._status:
case DeviceStatus.OFFLINE:
assert api_user.get_job(job_id).status == JobStatus.PENDING
case _:
assert api_user.get_job(job_id).status == JobStatus.CANCELLED


@pytest.mark.parametrize(
"device, user_role",
[(device, user_role) for user_role in list_user_roles() for device in get_devices_listing_params(user_role)],
)
@pytest.mark.slow
def test_only_owned_jobs_can_be_cancelled(user_role: UserRole, numpy_circuit: Circuit, device: Device, api: API):
"""Post a circuit and cancel it. This test proves that only admin users can cancel jobs submitted by admin users. This is a restricted case but is representative of how the permissions works --e.g qilimanjaro user cannot cancel a job submitted by a bsc user.
Args:
user_role (UserRole): _description_
numpy_circuit (Circuit): _description_
device (Device): _description_
"""
check_operation_possible_or_skip(Operation.CANCEL, device=device)
api_user = get_api_or_fail_test(get_logging_conf_or_fail_test(user_role=user_role))

api.select_device_id(device_id=device.id)
job_id = api.execute(circuit=numpy_circuit)[0]
match user_role:
case UserRole.ADMIN:
api_user.cancel_job(job_id=job_id)
match device._status:
case DeviceStatus.OFFLINE:
assert api_user.get_job(job_id).status == JobStatus.PENDING
case _:
assert api_user.get_job(job_id).status == JobStatus.CANCELLED
case _: # check only admin can delete jobs
with pytest.raises(BaseException):
api_user.cancel_job(job_id=job_id)


# ------------------------------------------------------------------------ OPERATION: GET RUNCARDS


Expand Down
5 changes: 2 additions & 3 deletions tests/end2end/utils/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Operation(Enum):
POST = "post"
RESPONSE = "response"
CHANGE_STATUS = "change_status"
CANCEL = "cancel"


def is_device(device: Device, status: DeviceStatus, availability: DeviceAvailability) -> bool:
Expand Down Expand Up @@ -88,9 +89,7 @@ def get_expected_operation_result( # pylint: disable=too-many-branches
"""

result: OperationResult = OperationResult.FORBIDDEN
if operation == Operation.SELECT:
result = OperationResult.SUCCESS
elif operation == Operation.POST:
if operation in [Operation.SELECT, Operation.POST, Operation.CANCEL]:
result = OperationResult.SUCCESS
elif operation == Operation.BLOCK:
if is_quantum(device) and not is_development():
Expand Down
4 changes: 3 additions & 1 deletion tests/end2end/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ def post_and_get_result(
circuit: Circuit | list[Circuit],
timeout: int = TIMEOUT,
call_every_seconds: int = CALL_EVERY_SECONDS,
name: str = "-",
summary: str = "-",
) -> JobData:
"""Post a circuit and tries to get the result. While he job is pending, retries during timeout
Expand All @@ -168,7 +170,7 @@ def post_and_get_result(
"""

api.select_device_id(device_id=device.id)
job_id = api.execute(circuit=circuit)[0]
job_id = api.execute(circuit=circuit, name=name, summary=summary)[0]

return get_job_result(api, job_id, timeout, call_every_seconds)

Expand Down
Loading

0 comments on commit b203074

Please sign in to comment.