/grader

golang coursera

Primary LanguageGoMIT LicenseMIT

Финальное задание по курсу https://golangcourse.ru/

Можно делать в свом репозитории, можно и нужно выложить (только его! не решения домашек) на гитхаб.

Проект значительно больше домашки и предполагает построение полноценного веб-приложения.

Требования по проекту:

  • В качестве структуры проекта надо взять https://github.com/golang-standards/project-layout. Не забудьте посмотреть рекомендуемые там видео.
  • Для управления зависимостиями используйте go modules (новая фича, в курсе не было, гуглить по golang modules), хотя вендоринг при помощи dep тоже можно.
  • Для проекта должен быть настроен golang-ci ( https://github.com/golangci/golangci-lint ) и выбранные вами проверки оттуда должны проходить. Большинство этих проверко подствечивается вам в VS Code или JetBrains GoLand
  • В проекте должны быть тесты. Максимально возможный %. Тесты не должны чисто быть для покрытия, они должны реально проверять что тестируемая логика работает.
  • (опционально) Если у вас много методов, который отвечают JSON-ом - иметь доку по методам в формате swagger.
  • (опционально) Если у вас много методов, который отвечают JSON-ом - генерировать сервер через swagger-доку, те подход schema-first.
  • (опционально) Статика встроена прямо в бинарь ( те вообще нет зависимостей ) через https://github.com/rakyll/statik или подобные библиотеки

Задания сознательно даются с неполным описанием, там надо додумать, допроектировать ( это тоже часть задания! ), задать вопросы.


Проект "Грейдер".

Это достаточно большое задание, в котором еще необходимо будет написать фронтенд. Но зато вы попробуете почти все что модно.

Это примерно то, чем людит пользуются при отправки решения домашек на разных образовательных платформах.

В качестве фронтенда возьмите фреймворк bootstrap (https://getbootstrap.com/), аякса не надо, все через полную перезагрузк страниц, JS тоже никакого не нужно. Я на нем делал формы в лекции с базами.

Суть заключается в следующем:

  • Есть страница с заданием (прямой переход по ссылке), там есть текст и форма загрузки файла.
  • Пользователь грузить файл, он добавляется в базу с пометкой "обрабатыватся" и в очередь на обработку.
  • Очередь (в виде отдельного сервиса) достает задачу на проверку и отправляет ее во внешний веб-сервис по описанному проктолу
  • Этот сервис выполняет задачу в докер-контейнере, и возвращает, соответственно, результат
  • Пользователь видит результат и дополнительное сообщение, если оно есть (например детали с ошибкой)

Будут следующие страницы (можно отклоняться, но не в стороту потери фич):

Пользовательская часть:

  • Авторизация
  • Регистрация
  • Список заданий, куда были загрузки
  • Страница задания, куда были загрузки - отображение списка загрузок с результатом
  • (опционально) Профиль пользователя
  • (опционально) Смена пароля

Админка:

  • Список заданий
  • Страница создания/редактирования задания ( там же - получение ссылки на задание )
  • Страница результатов задания - каким пользователем какое было выполнено - https://s.mail.ru/Fara/zjdHbywJo
  • (опционально) Много админов и задания принадлежат конкретному админу
  • (опционально) Список пользователей
  • (опционально) Назначение пользователя админом

Очередь:

  • Простой разгребатор очереди, который берет задание и отправляет во внешнюю систему

Внешний грейдер:

  • API, который принимает решение на проверку от основного сервера

Грейдерная часть основного сервера:

  • Метод, который принимает результат проверки задания (см выше)

Механика работы грейдера на сервере:

СЛОЖНЫЙ ВАРИАНТ ( ниже будет простой ):

Грейдер в задание настраивается при помощи следующего json-а в простом текстовом фоле ( или нескольких полей ) и валидацией на сервере.

{
    // адрес внешнего грейдера, куда будут отправлять на проверку задания
    // можно сделать через настройку в отдельном интерфейсе и указывать только имя а не урл каждый раз
    "external_grader": "https://127.0.0.1:8021/api/v1/grader",
    // список файлов, которые должен загрузить пользователь
    // обязательно проверять имя файла
    "files": [
        {
            "label": "hw1_game/main.go",
            "filename": "main.go"
        }
    ],
    // это дополнительная информация, которая уходит в грейдер
    "grader_payload": {
        // имя контейнера, который нужно запустить
        "container": "golangcourse_final",
        // в контейнер надо как опцию передать параметр partId с таким значением
        "partId": "HW1_game"
    }
}

Пример команды, которая должна вызваться при проверке:

docker run --user 1000 --network none --rm --name runXXXXXXXXXXXX \
  -v $FILE_PATH:/shared/submission/ \
  $CONTAINER partId $PART_ID
  • $FILE_PATH - пусть у вас на диске с файлом решения, который монтируется в контейнер как volume
  • $CONTAINER - параметр grader_payload.container из json выше
  • $PART_ID - параметр grader_payload.partId из json выше
  • Еще добавьте параметр с таймаутом
  • Эти параметры подставлять напрямую, а не через ENV - переменные через $ тут чтобы показать что сюда надо подставить что-то

Таким образом я могу одним контейнером проверять несколько заданий ( как и происходит в реальности ).

Запуск вышеописанной должен закончится с кодом 0 и следующим результатом:

// успешное выполнение
{
    "pass": true,
    "text": "Поздравляем! Вы успешно сделлаи задание"
}

// неуспешное выполнение
{
    "pass": false,
    "text": "FAIL\ncompilation error\n\nSTDOUT:\nFAIL	coursera/__grader/assigments/hw1_game [build failed]\n\n\nSTDERR:\n\n# coursera/__grader/assigments/hw1_game [coursera/__grader/assigments/hw1_game.test]\nassigments/hw1_game/main.go:82:1: syntax error: non-declaration statement outside function body\n"
}
  • text - выводится пользователю
  • pass - решение принято или нет

Если контейнер завершается с ненулевым кодом - значит FAIL + Internal error в описании


ПРОСТОЙ ВАРИАНТ №1:

Запускаем что-то в докере, который должен вернуть статус 0 ( успешно ) или не 0 ( фейл ). Например это может быть запуск тестов. Но в этом случае надо предусмотреть загрузку сопуствтующих файлов ( или иметь их в контейнере )

ПРОСТОЙ ВАРИАНТ №2:

Запускаем код пользователя в докере, на STDIN подаем туда что-то, сравниваем результат с эталоном в ситеме, если сошлось - ОК, нет - фейл.


Как только контейнер отработал - есть 2 варианта:

  1. С оффлайн разгребальщика ( который отдельный сервис) соединение висит Х минут ( таймаут в докере ), после чего обрывается с FAIL + timeout по решению. Если все ок - в решение проставляется соответствующий статус
  2. В грейдер приходит callback-url, который надо дернуть по завершению проверки решения. Не зубадьте тут авторизаци в виде какого-то JWT-токена с ограничением времени жизни

Общая схема работы: https://s.mail.ru/ALmB/g5EJe6GkB ( https://sequencediagram.org/ )

title Грейдер

User->Server: Загрузка решения
note right of Server: Сохраняем в базу
Server->QueueProcessor: RabbitMQ
QueueProcessor->Grader: HTTP с файлом и параметрами
Grader->Docker: Запуск контейнера
Docker->Grader: JSON c результатом
Grader->QueueProcessor: #1 HTTP ответ
Grader->Server: #2 через Callback-URL
Server->User: Результат решения

Эта последовательность разработки немного упростит вам работу:

  • начните с захардкоженным юзером
  • сделайте юзерскую часть интерфейса - пусть там из базы что-то фейковое берется на заранее добавленных данных
  • сделайте сервис очереди
  • сделайте сервис грейдера - пусть отдает фейковые данные
  • сделайте сервис грейдера чтобы работал полноценно с докером
  • сделайте работу с юзерами ( рега, авторизация, админы )

Бинарников у вас будет 3:

  1. Сервер куда ходят юзеры и админы
  2. Разгребатор очереди
  3. Грейдер

Совсем опциональные фичи:

  • Вход через oauth ( мейл, ВК, фейсбук) - https://github.com/golang/oauth2 - см в 3-й части курса
  • Несколько языков программирования в грейдере