Skip to content

Commit

Permalink
feat: add basic framework for k8s rest agent
Browse files Browse the repository at this point in the history
add basic source code for k8s rest agent
close #176

Signed-off-by: Haitao Yue <hightallyht@gmail.com>
  • Loading branch information
hightall committed Jul 30, 2020
1 parent aa8a09c commit abbdb13
Show file tree
Hide file tree
Showing 44 changed files with 1,335 additions and 0 deletions.
24 changes: 24 additions & 0 deletions build_image/docker/agent/k8s-rest-agent/Dockerfile.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3.8

COPY src/agent/k8s-rest-agent/requirements.txt /
COPY src/agent/k8s-rest-agent/pip /root/.pip

RUN pip install -r /requirements.txt

COPY src/agent/k8s-rest-agent/src /var/www/server
COPY src/agent/k8s-rest-agent/entrypoint.sh /
COPY src/agent/k8s-rest-agent/uwsgi/server.ini /etc/uwsgi/apps-enabled/
RUN mkdir /var/log/supervisor

ENV WEBROOT /
ENV WEB_CONCURRENCY 10
ENV DEBUG False
ENV UWSGI_WORKERS 1
ENV UWSGI_PROCESSES 1
ENV UWSGI_OFFLOAD_THREADS 10
ENV UWSGI_MODULE server.wsgi:application

WORKDIR /var/www/server
RUN python manage.py collectstatic --noinput

CMD bash /entrypoint.sh
24 changes: 24 additions & 0 deletions src/agent/k8s-rest-agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM python:3.8

COPY requirements.txt /
COPY pip /root/.pip

RUN pip install -r /requirements.txt

COPY src /var/www/server
COPY entrypoint.sh /
COPY uwsgi/server.ini /etc/uwsgi/apps-enabled/
RUN mkdir /var/log/supervisor

ENV WEBROOT /
ENV WEB_CONCURRENCY 10
ENV DEBUG False
ENV UWSGI_WORKERS 1
ENV UWSGI_PROCESSES 1
ENV UWSGI_OFFLOAD_THREADS 10
ENV UWSGI_MODULE server.wsgi:application

WORKDIR /var/www/server
RUN python manage.py collectstatic --noinput

CMD bash /entrypoint.sh
11 changes: 11 additions & 0 deletions src/agent/k8s-rest-agent/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

if [[ "$RUN_TYPE" == "SERVER" ]]; then
uwsgi --ini /etc/uwsgi/apps-enabled/server.ini;
else
if [[ "$RUN_TYPE" == "TASK" ]]; then
celery -A server worker --autoscale=20,6 -l info
elif [[ "$RUN_TYPE" == "BEAT_TASK" ]]; then
celery -A server beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler --pidfile=/opt/celeryd.pid
fi
fi
5 changes: 5 additions & 0 deletions src/agent/k8s-rest-agent/pip/pip.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[global]
index-url=http://mirrors.cloud.aliyuncs.com/pypi/simple/

[install]
trusted-host=mirrors.cloud.aliyuncs.com
18 changes: 18 additions & 0 deletions src/agent/k8s-rest-agent/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Django>=3.0
uwsgi
enum34
djangorestframework
holdup>1.5.0,<=1.6.0
drf-yasg<=1.17.0
swagger_spec_validator<=2.4.1
psycopg2-binary
celery<5.0,>=4.4
redis
requests
supervisor
django-celery-beat
django-celery-results
django-3-jet
djangorestframework-jwt<=1.11.0
python-jwt # 需要安装,否则会出现token解码失败错误
shortuuid
Empty file.
3 changes: 3 additions & 0 deletions src/agent/k8s-rest-agent/src/api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
5 changes: 5 additions & 0 deletions src/agent/k8s-rest-agent/src/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = "api"
67 changes: 67 additions & 0 deletions src/agent/k8s-rest-agent/src/api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import base64
import json
import logging

from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
from rest_framework import authentication
from rest_framework import exceptions
from rest_framework_jwt.authentication import (
JSONWebTokenAuthentication as CoreJSONWebTokenAuthentication,
)
from rest_framework_jwt.settings import api_settings

jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
User = get_user_model()

LOG = logging.getLogger(__name__)


class JSONWebTokenAuthentication(CoreJSONWebTokenAuthentication):
@staticmethod
def _get_or_create_user(user_id, payload=None):
if payload is None:
payload = {}

user, _ = User.objects.get_or_create(
id=user_id, username=user_id, defaults={"password": user_id}
)

return user

def authenticate_credentials(self, payload):
"""
Returns an active user that matches the payload's user id and email.
"""
username = jwt_get_username_from_payload(payload)

if not username:
msg = _("Invalid payload.")
raise exceptions.AuthenticationFailed(msg)

user = self._get_or_create_user(username, payload)

if not user.is_active:
msg = _("User account is disabled.")
raise exceptions.AuthenticationFailed(msg)

return user


class IstioJWTAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
token = request.META.get("HTTP_TOKEN", None)
if token is None:
return None

token += "=" * (-len(token) % 4)
token = base64.b64decode(token)
token = json.loads(token)
user_id = token.get("sub", None)
if user_id is None:
return None
user, _ = User.objects.get_or_create(
id=user_id, username=user_id, defaults={"password": user_id}
)
return user, None
Empty file.
Empty file.
14 changes: 14 additions & 0 deletions src/agent/k8s-rest-agent/src/api/management/commands/test_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.core.management import BaseCommand
from api.tasks import example_task
from django_celery_beat.models import IntervalSchedule, PeriodicTask


class Command(BaseCommand):
help = "Test Task"

def handle(self, *args, **options):
interval = IntervalSchedule.objects.first()
PeriodicTask.objects.create(
interval=interval, name="example", task="server.tasks.example_task"
)
# example_task.delay()
Empty file.
1 change: 1 addition & 0 deletions src/agent/k8s-rest-agent/src/api/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .user import User, Profile
42 changes: 42 additions & 0 deletions src/agent/k8s-rest-agent/src/api/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models.signals import post_save
from api.utils.db_functions import make_uuid


class User(AbstractUser):
roles = []

id = models.UUIDField(
primary_key=True,
help_text="ID of user",
default=make_uuid,
editable=True,
)
username = models.CharField(default="", max_length=128, unique=True)

def __str__(self):
return self.username


class Profile(models.Model):
user = models.OneToOneField(
User, related_name="profile", on_delete=models.CASCADE
)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return "%s's profile" % self.user

class Meta:
ordering = ("-created_at",)


def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)


post_save.connect(create_user_profile, sender=User)

# Create your models here.
Empty file.
Empty file.
40 changes: 40 additions & 0 deletions src/agent/k8s-rest-agent/src/api/routes/hello/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import logging
import os

from rest_framework import viewsets, status
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from api.auth import JSONWebTokenAuthentication, IstioJWTAuthentication
from api.utils.mixins import PermissionsPerMethodMixin

LOG = logging.getLogger(__name__)
APP_VERSION = os.getenv("APP_VERSION", "v1")


class HelloViewSet(PermissionsPerMethodMixin, viewsets.ViewSet):
authentication_classes = (IstioJWTAuthentication,)

@swagger_auto_schema(
operation_summary="Hello world", operation_description="Hello world"
)
def list(self, request):
return Response(
{"hello": "world %s" % APP_VERSION}, status=status.HTTP_200_OK
)

@swagger_auto_schema(operation_summary="hello world need auth")
@action(
methods=["get"],
url_path="need-auth",
url_name="need-auth",
detail=False,
)
# @permission_classes((IsAuthenticated,))
def need_auth(self, request):
LOG.info("request user %s", request.user)
return Response(
{"hello": "auth world %s" % APP_VERSION}, status=status.HTTP_200_OK
)
1 change: 1 addition & 0 deletions src/agent/k8s-rest-agent/src/api/tasks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from api.tasks.task.example import example_task
Empty file.
12 changes: 12 additions & 0 deletions src/agent/k8s-rest-agent/src/api/tasks/task/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import logging

from server.celery import app


LOG = logging.getLogger(__name__)


@app.task(name="example_task")
def example_task():
LOG.info("example task")
return True
3 changes: 3 additions & 0 deletions src/agent/k8s-rest-agent/src/api/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
Empty file.
2 changes: 2 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .swagger import with_common_response
from .db import paginate_list
26 changes: 26 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/common/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.core.paginator import Paginator
from django.db.models import Func


class Round(Func):
function = "ROUND"
arity = 2


def paginate_list(data=None, page=1, per_page=10, limit=None):
if not data:
data = []

total = len(data)

if per_page != -1:
p = Paginator(data, per_page)
last_page = p.page_range[-1]
page = page if page <= last_page else last_page
data = p.page(page)
total = p.count
else:
if limit:
data = data[:limit]

return data, total
62 changes: 62 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/common/swagger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from drf_yasg import openapi
from rest_framework import serializers
from rest_framework import status

from api.utils.serializers import BadResponseSerializer

basic_type_info = [
(serializers.CharField, openapi.TYPE_STRING),
(serializers.BooleanField, openapi.TYPE_BOOLEAN),
(serializers.IntegerField, openapi.TYPE_INTEGER),
(serializers.FloatField, openapi.TYPE_NUMBER),
(serializers.FileField, openapi.TYPE_FILE),
(serializers.ImageField, openapi.TYPE_FILE),
]


def to_form_paras(self):
custom_paras = []
for field_name, field in self.fields.items():
type_str = openapi.TYPE_STRING
for field_class, type_format in basic_type_info:
if isinstance(field, field_class):
type_str = type_format
help_text = getattr(field, "help_text")
default = getattr(field, "default", None)
required = getattr(field, "required")
if callable(default):
custom_paras.append(
openapi.Parameter(
field_name,
openapi.IN_FORM,
help_text,
type=type_str,
required=required,
)
)
else:
custom_paras.append(
openapi.Parameter(
field_name,
openapi.IN_FORM,
help_text,
type=type_str,
required=required,
default=default,
)
)
return custom_paras


def with_common_response(responses=None):
if responses is None:
responses = {}

responses.update(
{
status.HTTP_400_BAD_REQUEST: BadResponseSerializer,
status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal Error",
}
)

return responses
14 changes: 14 additions & 0 deletions src/agent/k8s-rest-agent/src/api/utils/db_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import uuid
import shortuuid


def make_uuid():
return str(uuid.uuid4())


def make_uuid_hex():
return uuid.uuid4().hex


def make_short_uuid():
return shortuuid.ShortUUID().random(length=16)
Loading

0 comments on commit abbdb13

Please sign in to comment.