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

Device info versioning #8222

Merged
merged 3 commits into from
Jul 26, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 11 additions & 2 deletions kolibri/core/discovery/utils/network/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import requests
from six.moves.urllib.parse import urljoin
from six.moves.urllib.parse import urlparse

from . import errors
from .urls import get_normalized_url_variations
Expand Down Expand Up @@ -32,6 +33,9 @@ def __init__(self, base_url=None, address=None, timeout=None, **kwargs):
)

def _attempt_connections(self, urls):
from kolibri.core.public.utils import DEVICE_INFO_VERSION
from kolibri.core.public.utils import device_info_keys

# try each of the URLs in turn, returning the first one that succeeds
for url in urls:
try:
Expand All @@ -41,12 +45,17 @@ def _attempt_connections(self, urls):
base_url=url,
timeout=self.timeout,
allow_redirects=True,
params={"v": DEVICE_INFO_VERSION},
)
# check that we successfully connected, and if we were redirected that it's still the right endpoint
if response.status_code == 200 and response.url.rstrip("/").endswith(
response_path = urlparse(response.url).path
if response.status_code == 200 and response_path.rstrip("/").endswith(
"/api/public/info"
):
self.info = response.json()
info = response.json()
self.info = {}
for key in device_info_keys.get(DEVICE_INFO_VERSION, []):
self.info[key] = info.get(key)
if self.info["application"] not in ["studio", "kolibri"]:
raise requests.RequestException(
"Server is not running Kolibri or Studio"
Expand Down
5 changes: 4 additions & 1 deletion kolibri/core/public/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ class InfoViewSet(viewsets.ViewSet):

def list(self, request):
"""Returns metadata information about the device"""
# Default to version 1, as earlier versions of Kolibri
# will not have sent a "v" query param.
version = request.query_params.get("v", "1")

return Response(get_device_info())
return Response(get_device_info(version))


def _get_channel_list(version, params, identifier=None):
Expand Down
27 changes: 26 additions & 1 deletion kolibri/core/public/test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def setUp(self):
)
set_channel_metadata_fields(channel2.id, public=True)

def test_info_endpoint(self):
def test_info_endpoint_unversioned(self):
response = self.client.get(reverse("kolibri:core:info-list"))
instance_model = InstanceIDModel.get_or_create_current_instance()[0]
settings = DeviceSettings.objects.get()
Expand All @@ -107,6 +107,31 @@ def test_info_endpoint(self):
self.assertEqual(response.data["instance_id"], instance_model.id)
self.assertEqual(response.data["device_name"], settings.name)
self.assertEqual(response.data["operating_system"], platform.system())
self.assertIsNone(response.data.get("subset_of_users_device"))

def test_info_endpoint_v1(self):
response = self.client.get(reverse("kolibri:core:info-list"), data={"v": "1"})
instance_model = InstanceIDModel.get_or_create_current_instance()[0]
settings = DeviceSettings.objects.get()
self.assertEqual(response.data["application"], "kolibri")
self.assertEqual(response.data["kolibri_version"], kolibri.__version__)
self.assertEqual(response.data["instance_id"], instance_model.id)
self.assertEqual(response.data["device_name"], settings.name)
self.assertEqual(response.data["operating_system"], platform.system())
self.assertIsNone(response.data.get("subset_of_users_device"))

def test_info_endpoint_v2(self):
response = self.client.get(reverse("kolibri:core:info-list"), data={"v": "2"})
instance_model = InstanceIDModel.get_or_create_current_instance()[0]
settings = DeviceSettings.objects.get()
self.assertEqual(response.data["application"], "kolibri")
self.assertEqual(response.data["kolibri_version"], kolibri.__version__)
self.assertEqual(response.data["instance_id"], instance_model.id)
self.assertEqual(response.data["device_name"], settings.name)
self.assertEqual(response.data["operating_system"], platform.system())
self.assertEqual(
response.data["subset_of_users_device"], settings.subset_of_users_device
)

def test_public_channel_list(self):
response = self.client.get(get_channel_lookup_url(baseurl="/"))
Expand Down
42 changes: 39 additions & 3 deletions kolibri/core/public/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,37 @@
logger = logging.getLogger(__name__)


def get_device_info():
"""Returns metadata information about the device"""
device_info_keys = {
"1": [
"application",
"kolibri_version",
"instance_id",
"device_name",
"operating_system",
],
"2": [
"application",
"kolibri_version",
"instance_id",
"device_name",
"operating_system",
"subset_of_users_device",
],
}

DEVICE_INFO_VERSION = "2"


def get_device_info(version=DEVICE_INFO_VERSION):
"""
Returns metadata information about the device
The default kwarg version should always be the latest
version of device info that this function supports.
We maintain historic versions for backwards compatibility
"""

if version not in device_info_keys:
version = DEVICE_INFO_VERSION

instance_model = InstanceIDModel.get_or_create_current_instance()[0]
try:
Expand All @@ -39,14 +68,21 @@ def get_device_info():
device_name = instance_model.hostname
subset_of_users_device = False

info = {
all_info = {
"application": "kolibri",
"kolibri_version": kolibri.__version__,
"instance_id": instance_model.id,
"device_name": device_name,
"operating_system": platform.system(),
"subset_of_users_device": subset_of_users_device,
}

info = {}

# By this point, we have validated that the version is in device_info_keys
for key in device_info_keys.get(version, []):
info[key] = all_info[key]

return info


Expand Down
5 changes: 3 additions & 2 deletions kolibri/utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import signal
import sys
import traceback

import click
from django.core.management import execute_from_command_line
Expand Down Expand Up @@ -184,8 +185,8 @@ def __init__(self, *args, **kwargs):
def invoke(self, ctx):
try:
initialize(**get_initialize_params())
except Exception as e:
raise click.ClickException(e)
except Exception:
raise click.ClickException(traceback.format_exc())

# Remove parameters that are not for Django management command
for param in initialize_params:
Expand Down