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

Zeroconf-based Kolibri P2P discovery system #5660

Closed
wants to merge 15 commits into from

Conversation

jamalex
Copy link
Member

@jamalex jamalex commented Jun 12, 2019

Summary

We support local network peer connections for content import, and soon, for data syncing. But right now, it's necessary to manually enter the IP and port, which is at best inconvenient, and at worst an insurmountable barrier for many users.

Enter zeroconf! It's specifically designed for this type of situation -- allowing nearby devices on the same network to discover one another and the services they offer.

This PR is a preliminary, but usable, backend implementation of a zeroconf-based "Kolibri service broadcasting and discovery" system. When Kolibri starts up, it will register itself on the local network, and then monitor and track any other instances of Kolibri on the same local network. The frontend code can then call an API endpoint to enumerate the nearby Kolibri instances, including information about what IP/port they're listening on, and what facilities/channels they have loaded on them.

One of the lovely side-effects of using zeroconf (aka mDNS) for this is that it also ends up creating local
area domain names that can be resolved and used in the browser at least on Linux and OSX:
image

Note: includes some changes I made to https://github.com/learningequality/python-zeroconf

Reviewer guidance

To test, you'll want to spin up a couple instances of this. To start with, these could be on the same computer, just using different ports and KOLIBRI_HOME directories, e.g.:

# run this in one terminal tab
kolibri start

# run this in another tab
KOLIBRI_HOME=~/.kolibri-temp KOLIBRI_LISTEN_PORT=9090 kolibri start

Then, login as an admin on each and go to:
http://127.0.0.1:8080/api/discovery/networksearch/
and
http://127.0.0.1:9090/api/discovery/networksearch/

and you should see something like this:

[
    {
        "ip": "192.168.1.25",
        "self": true,
        "data": {
            "channels": [
                {
                    "id": "c7eda62c6489554a941058fa883e7c2c",
                    "name": "Better World Ed"
                },
                {
                    "id": "74f36493bb475b62935fa8705ed59fed",
                    "name": "Thoughtful Learning"
                },
                {
                    "id": "f9da12749d995fa197f8b4c0192e7b2c",
                    "name": "PraDigi"
                }
            ],
            "facilities": [
                {
                    "dataset_id": "d265a3809dd02de42570ef5ba5b1bb86",
                    "id": "318aa7335a2ad82434dee8534caf85d9",
                    "name": "a"
                }
            ]
        },
        "port": 8080,
        "host": "b053.kolibri.local",
        "local": true,
        "id": "b053"
    }
]

Contributor Checklist

PR process:

  • PR has the correct target branch and milestone
  • PR has 'needs review' or 'work-in-progress' label
  • If PR is ready for review, a reviewer has been added. (Don't use 'Assignees')
  • If this is an important user-facing change, PR or related issue has a 'changelog' label
  • If this includes an internal dependency change, a link to the diff is provided

Testing:

  • Contributor has fully tested the PR manually
  • If there are any front-end changes, before/after screenshots are included
  • Critical user journeys are covered by Gherkin stories
  • Critical and brittle code paths are covered by unit tests

Reviewer Checklist

  • Automated test coverage is satisfactory
  • PR is fully functional
  • PR has been tested for accessibility regressions
  • External dependency files were updated if necessary (yarn and pip)
  • Documentation is updated
  • Contributor is in AUTHORS.md

TODO:

  • Add an option in options.ini to disable zeroconf
  • Consider switching service type to _http._tcp.local so it's standardized.
  • Rebroadcast service when channels or facilities change

@jamalex jamalex added the TODO: needs review Waiting for review label Jun 12, 2019
@jamalex jamalex added this to the 0.13.0 milestone Jun 12, 2019
@jamalex jamalex changed the title Zeroconf Zeroconf-based Kolibri P2P discovery system (backend only) Jun 12, 2019
@codecov
Copy link

codecov bot commented Jun 12, 2019

Codecov Report

Merging #5660 into develop will increase coverage by 0.13%.
The diff coverage is 85.71%.

Impacted Files Coverage Δ
kolibri/core/discovery/api.py 100% <100%> (ø) ⬆️
kolibri/core/discovery/api_urls.py 100% <100%> (ø) ⬆️
kolibri/utils/server.py 46.01% <25%> (-0.68%) ⬇️
kolibri/utils/cli.py 66.14% <57.14%> (-0.63%) ⬇️
kolibri/utils/system.py 40.62% <77.77%> (+6.72%) ⬆️
kolibri/core/discovery/utils/network/search.py 92.85% <92.85%> (ø)
kolibri/utils/env.py 87.5% <0%> (-6.25%) ⬇️
...olibri/core/content/utils/import_export_content.py 84.48% <0%> (-1.73%) ⬇️
test/__init__.py
... and 2 more

@codecov
Copy link

codecov bot commented Jun 12, 2019

Codecov Report

Merging #5660 into develop will increase coverage by 20.34%.
The diff coverage is 83.1%.

Impacted Files Coverage Δ
kolibri/core/discovery/api.py 100% <100%> (ø) ⬆️
kolibri/core/discovery/api_urls.py 100% <100%> (ø) ⬆️
kolibri/utils/server.py 46.01% <25%> (-0.68%) ⬇️
kolibri/utils/cli.py 65.94% <46.66%> (-0.83%) ⬇️
kolibri/utils/system.py 40.62% <77.77%> (+6.72%) ⬆️
kolibri/core/discovery/utils/network/search.py 92.85% <92.85%> (ø)
kolibri/core/apps.py 91.3% <0%> (-8.7%) ⬇️
kolibri/core/content/test/sqlalchemytesting.py 84% <0%> (-8%) ⬇️
kolibri/core/deviceadmin/utils.py 93.58% <0%> (-5.13%) ⬇️
kolibri/core/notifications/models.py 87.67% <0%> (-4.11%) ⬇️
... and 552 more

return instances


def register_zeroconf_service(port, id):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this function need the port as a parameter?
Is not possible to get it from the options or reading it from the PID_FILE as it is done in other parts of the code?
This port option pushes changes in many parts of this PR code that might not be needed.

Copy link
Member Author

@jamalex jamalex Jun 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, PID_FILE could be a good place, yes. I originally wanted to pull it from conf.OPTIONS, except it can be overridden with --port=XYZ, which doesn't get stored anywhere (except in the PID file, I guess), just passed in to the start command function as an argument, which was the pattern I followed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I understand your concerns but, as I understand the code, the value in the PID file is affected both by conf.OPTIONS and env variables, thus it should be the real value for the port. Taking it from there would avoid many modifications you had to do just to drag the port parameter here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, agreed! Can't see any issues with that approach. Will update.

The PID is also written into there for kolibri-server, yes? Based on what uwsgi/nginx are exposing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kolibri-server takes the value from conf.OPTIONS because there's no warranty the part of code in Kolibri that saves the PID has been executed before kolibri-server starts. The env variable option does not have sense when running it as a daemon.
After kolibri finishes the starting process, the PID file will have the same value that is in conf.OPTIONS so I think we can consider we are safe on this side too.

kolibri/utils/cli.py Show resolved Hide resolved
Copy link
Contributor

@benjaoming benjaoming left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks so promising!! Am already thinking about what it can contribute to in terms of documentation simplification about static IPs -- that if MDNS is supported widely (IDK) then we could implement the naming of a device in the setup process <unique-device-name>.kolibri.local.

kolibri/utils/server.py Show resolved Hide resolved
requirements/base.txt Outdated Show resolved Hide resolved
@benjaoming
Copy link
Contributor

benjaoming commented Jun 18, 2019

@jamalex a quick idea: When using custom names, could the device try to look up its own name and report back if there is a conflict?

Scratch that, you already knew 💯

assert self.info is None, "Service is already registered!"

i = 1
id = self.id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assigns to a builtin, or is it just me?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems so, yep! Perhaps I'll rename the local var to unique_id

@jamalex jamalex changed the title Zeroconf-based Kolibri P2P discovery system (backend only) Zeroconf-based Kolibri P2P discovery system Jun 20, 2019
@jamalex
Copy link
Member Author

jamalex commented Jun 20, 2019

This looks so promising!! Am already thinking about what it can contribute to in terms of documentation simplification about static IPs -- that if MDNS is supported widely (IDK) then we could implement the naming of a device in the setup process .kolibri.local

It's sooooooo close to a possibility, EXCEPT that Android doesn't implement any version of mDNS/zeroconf/etc.... and that's one of our most common client devices. 😿

What I'm thinking is that we could make lightweight "launcher" apps for Android/Windows/Linux that do nothing other than quickly scan the local network and find the IP for a Kolibri instance (possibly caching/preferring a specific one after first use), and then pop it open in the web browser. Would be a huge timesaver and headache-avoider in itself.

@indirectlylit
Copy link
Contributor

@rtibbles how do you think this work will be affected by #6007

@rtibbles
Copy link
Member

Looks like any background tasks are managed inside zeroconf - so, apart from superficial merge conflicts, I don't think it would be affected.

@indirectlylit
Copy link
Contributor

Looks like any background tasks are managed inside zeroconf - so, apart from superficial merge conflicts, I don't think it would be affected.

okay cool. wasn't sure if you were going to want to leverage iceqube machinery for this

@indirectlylit indirectlylit mentioned this pull request Oct 30, 2019
9 tasks
@indirectlylit
Copy link
Contributor

migrating to #6011 and https://github.com/learningequality/kolibri/tree/zeroconf so that @micahscopes can work on it

@jonboiser jonboiser removed the TODO: needs review Waiting for review label Dec 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants