feat: remove oracle dyn port

pull/15144/head
Aaron3S 2025-04-01 17:32:29 +08:00 committed by Bryan
parent 623c800d31
commit 4fd8efd043
12 changed files with 26 additions and 268 deletions

View File

@ -2,6 +2,5 @@
# #
from .applet import * from .applet import *
from .component import * from .component import *
from .db_listen_port import *
from .session import * from .session import *
from .virtualapp import * from .virtualapp import *

View File

@ -1,34 +0,0 @@
# coding: utf-8
#
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from assets.serializers.asset.database import DatabaseWithGatewaySerializer
from orgs.utils import tmp_to_org
from ..utils import db_port_manager, DBPortManager
db_port_manager: DBPortManager
__all__ = ['DBListenPortViewSet']
class DBListenPortViewSet(GenericViewSet):
rbac_perms = {
'*': ['assets.view_asset'],
}
http_method_names = ['get']
def list(self, request, *args, **kwargs):
ports = db_port_manager.get_already_use_ports()
return Response(data=ports, status=status.HTTP_200_OK)
@action(methods=['get'], detail=False, url_path='db-info')
def db_info(self, request, *args, **kwargs):
port = request.query_params.get("port")
db = db_port_manager.get_db_by_port(port)
with tmp_to_org(db.org):
serializer = DatabaseWithGatewaySerializer(instance=db)
return Response(data=serializer.data, status=status.HTTP_200_OK)

View File

@ -0,0 +1,20 @@
# Generated by Django 4.1.13 on 2025-04-01 07:44
import common.db.fields
import django.core.validators
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('terminal', '0005_endpoint_vnc_port'),
]
operations = [
migrations.AddField(
model_name='endpoint',
name='oracle_port',
field=common.db.fields.PortField(default=15210, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle port'),
),
]

View File

@ -22,6 +22,7 @@ class Endpoint(JMSBaseModel):
postgresql_port = PortField(default=54320, verbose_name=_('PostgreSQL port')) postgresql_port = PortField(default=54320, verbose_name=_('PostgreSQL port'))
redis_port = PortField(default=63790, verbose_name=_('Redis port')) redis_port = PortField(default=63790, verbose_name=_('Redis port'))
sqlserver_port = PortField(default=14330, verbose_name=_('SQLServer port')) sqlserver_port = PortField(default=14330, verbose_name=_('SQLServer port'))
oracle_port = PortField(default=15210,verbose_name=_('Oracle port'))
vnc_port = PortField(default=15900, verbose_name=_('VNC port')) vnc_port = PortField(default=15900, verbose_name=_('VNC port'))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
@ -37,17 +38,10 @@ class Endpoint(JMSBaseModel):
return self.name return self.name
def get_port(self, target_instance, protocol): def get_port(self, target_instance, protocol):
from terminal.utils import db_port_manager from assets.const import Protocol
from assets.const import DatabaseTypes, Protocol if protocol in [Protocol.sftp, Protocol.telnet]:
protocol = Protocol.ssh
if isinstance(target_instance, Asset) and \ port = getattr(self, f'{protocol}_port', 0)
target_instance.is_type(DatabaseTypes.ORACLE) and \
protocol == Protocol.oracle:
port = db_port_manager.get_port_by_db(target_instance)
else:
if protocol in [Protocol.sftp, Protocol.telnet]:
protocol = Protocol.ssh
port = getattr(self, f'{protocol}_port', 0)
return port return port
def is_default(self): def is_default(self):

View File

@ -5,30 +5,18 @@ from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
from common.serializers import BulkModelSerializer from common.serializers import BulkModelSerializer
from common.serializers.fields import ObjectRelatedField from common.serializers.fields import ObjectRelatedField
from ..models import Endpoint, EndpointRule from ..models import Endpoint, EndpointRule
from ..utils import db_port_manager
__all__ = ['EndpointSerializer', 'EndpointRuleSerializer'] __all__ = ['EndpointSerializer', 'EndpointRuleSerializer']
class EndpointSerializer(BulkModelSerializer): class EndpointSerializer(BulkModelSerializer):
# 解决 luna 处理繁琐的问题, 返回 magnus 监听的当前 db 的 port
oracle_port = serializers.SerializerMethodField(label=_('Oracle port'))
oracle_port_range = serializers.CharField(
max_length=128, default=db_port_manager.oracle_port_range, read_only=True,
label=_('Oracle port range'),
help_text=_(
'Oracle proxy server listen port is dynamic, Each additional Oracle '
'database instance adds a port listener'
)
)
class Meta: class Meta:
model = Endpoint model = Endpoint
fields_mini = ['id', 'name'] fields_mini = ['id', 'name']
fields_small = [ fields_small = [
'host', 'https_port', 'http_port', 'ssh_port', 'rdp_port', 'host', 'https_port', 'http_port', 'ssh_port', 'rdp_port',
'mysql_port', 'mariadb_port', 'postgresql_port', 'redis_port', 'vnc_port', 'mysql_port', 'mariadb_port', 'postgresql_port', 'redis_port', 'vnc_port',
'oracle_port_range', 'oracle_port', 'sqlserver_port', 'is_active' 'oracle_port', 'sqlserver_port', 'is_active'
] ]
fields = fields_mini + fields_small + [ fields = fields_mini + fields_small + [
'comment', 'date_created', 'date_updated', 'created_by' 'comment', 'date_created', 'date_updated', 'created_by'
@ -41,13 +29,6 @@ class EndpointSerializer(BulkModelSerializer):
) )
}, },
} }
def get_oracle_port(self, obj: Endpoint):
view = self.context.get('view')
if not view or view.action not in ['smart']:
return 0
return obj.get_port(view.target_instance, view.target_protocol)
def get_extra_kwargs(self): def get_extra_kwargs(self):
extra_kwargs = super().get_extra_kwargs() extra_kwargs = super().get_extra_kwargs()
model_fields = self.Meta.model._meta.fields model_fields = self.Meta.model._meta.fields

View File

@ -1,5 +1,4 @@
from .applet import * from .applet import *
from .db_port import *
from .session import * from .session import *
from .session_sharing import * from .session_sharing import *
from .terminal import * from .terminal import *

View File

@ -12,9 +12,7 @@ from orgs.utils import tmp_to_builtin_org
from users.models import User from users.models import User
from ..models import Applet, AppletHost from ..models import Applet, AppletHost
from ..tasks import applet_host_generate_accounts from ..tasks import applet_host_generate_accounts
from ..utils import DBPortManager
db_port_manager: DBPortManager
logger = get_logger(__file__) logger = get_logger(__file__)

View File

@ -1,38 +0,0 @@
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from assets.models import Asset, Database
from common.decorators import on_transaction_commit
from common.signals import django_ready
from common.utils import get_logger
from ..utils import db_port_manager
logger = get_logger(__file__)
@receiver(django_ready)
def check_db_port_mapper(sender, **kwargs):
logger.info('Check oracle ports (MAGNUS_ORACLE_PORTS)')
try:
db_port_manager.check()
except Exception as e:
# 新部署会显示 assets_database 表不存在
logger.warning('(Ignore) {}'.format(e))
@receiver(post_save, sender=Database)
def on_db_created(sender, instance: Database, created, **kwargs):
if instance.type != 'oracle':
return
if not created:
return
logger.info("Oracle create signal recv: {} {}".format(instance, instance.type))
db_port_manager.check()
@receiver(post_delete, sender=Database)
def on_db_delete(sender, instance, **kwargs):
if instance.type != 'oracle':
return
logger.info("Oracle delete signal recv: {}".format(instance))
db_port_manager.check()

View File

@ -7,9 +7,6 @@ from common.utils import get_logger
from common.utils.connection import RedisPubSub from common.utils.connection import RedisPubSub
from ..const import TaskNameType from ..const import TaskNameType
from ..models import Task, Session from ..models import Task, Session
from ..utils import DBPortManager
db_port_manager: DBPortManager
logger = get_logger(__file__) logger = get_logger(__file__)

View File

@ -29,7 +29,6 @@ router.register(r'applet-hosts/((?P<host>[^/.]+)/)?applets', api.AppletHostApple
router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host')
router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication') router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication')
router.register(r'applet-host-deployments', api.AppletHostDeploymentViewSet, 'applet-host-deployment') router.register(r'applet-host-deployments', api.AppletHostDeploymentViewSet, 'applet-host-deployment')
router.register(r'db-listen-ports', api.DBListenPortViewSet, 'db-listen-ports')
router.register(r'virtual-apps', api.VirtualAppViewSet, 'virtual-app') router.register(r'virtual-apps', api.VirtualAppViewSet, 'virtual-app')
router.register(r'app-providers', api.AppProviderViewSet, 'app-provider') router.register(r'app-providers', api.AppProviderViewSet, 'app-provider')
router.register(r'app-providers/((?P<provider>[^/.]+)/)?apps', api.AppProviderAppViewSet, 'app-provider-app') router.register(r'app-providers/((?P<provider>[^/.]+)/)?apps', api.AppProviderAppViewSet, 'app-provider-app')

View File

@ -1,3 +1,2 @@
from .components import * from .components import *
from .common import * from .common import *
from .db_port_mapper import *

View File

@ -1,156 +0,0 @@
from django.conf import settings
from django.core.cache import cache
from django.utils.translation import gettext_lazy as _
from assets.const import DatabaseTypes
from assets.models import Database
from common.decorators import Singleton
from common.exceptions import JMSException
from common.utils import get_logger, get_object_or_none
from orgs.utils import tmp_to_root_org
logger = get_logger(__file__)
@Singleton
class DBPortManager:
""" 管理端口-数据库ID的映射, Magnus 要使用 """
CACHE_KEY = 'PORT_DB_MAPPER'
def __init__(self):
oracle_ports = self.oracle_port_range
try:
port_start, port_end = oracle_ports.split('-')
port_start, port_end = int(port_start), int(port_end)
except Exception as e:
logger.error('MAGNUS_ORACLE_PORTS config error: {}'.format(e))
port_start, port_end = 30000, 30100
self.port_start, self.port_end = port_start, port_end
# 可以使用的端口列表
self.all_avail_ports = list(range(self.port_start, self.port_end + 1))
@property
def oracle_port_range(self):
oracle_ports = settings.MAGNUS_ORACLE_PORTS
if not oracle_ports and settings.MAGNUS_PORTS:
oracle_ports = settings.MAGNUS_PORTS
return oracle_ports
@staticmethod
def fetch_dbs():
with tmp_to_root_org():
dbs = Database.objects.filter(platform__type=DatabaseTypes.ORACLE).order_by('id')
return dbs
def check(self):
dbs = self.fetch_dbs()
mapper = self.get_mapper()
db_ids = [str(db.id) for db in dbs]
db_ids_to_add = list(set(db_ids) - set(mapper.values()))
mapper = self.bulk_add(db_ids_to_add, mapper)
db_ids_to_pop = set(mapper.values()) - set(db_ids)
mapper = self.bulk_pop(db_ids_to_pop, mapper)
if db_ids_to_add or db_ids_to_pop:
self.set_mapper(mapper)
if settings.DEBUG:
logger.debug("Oracle listen ports: {}".format(len(mapper.keys())))
def init(self):
dbs = self.fetch_dbs()
db_ids = dbs.values_list('id', flat=True)
db_ids = [str(i) for i in db_ids]
mapper = dict(zip(self.all_avail_ports, list(db_ids)))
self.set_mapper(mapper)
return mapper
def bulk_add(self, db_ids, mapper):
for db_id in db_ids:
avail_port = self.get_next_avail_port(mapper)
mapper[avail_port] = str(db_id)
return mapper
def bulk_pop(self, db_ids, mapper):
new_mapper = {port: str(db_id) for port, db_id in mapper.items() if db_id not in db_ids}
return new_mapper
def get_port_by_db(self, db, raise_exception=True):
mapper = self.get_mapper()
for port, db_id in mapper.items():
if db_id == str(db.id):
return port
if raise_exception:
error = _(
'No available port is matched. '
'The number of databases may have exceeded the number of ports '
'open to the database agent service, '
'Contact the administrator to open more ports.'
)
raise JMSException(error)
def get_db_by_port(self, port):
try:
port = int(port)
except Exception as e:
raise JMSException('Port type error: {}'.format(e))
mapper = self.get_mapper()
db_id = mapper.get(port, None)
if not db_id:
raise JMSException('Database not in port-db mapper, port: {}'.format(port))
with tmp_to_root_org():
db = get_object_or_none(Database, id=db_id)
if not db:
raise JMSException('Database not exists, db id: {}'.format(db_id))
return db
def get_next_avail_port(self, mapper=None):
if mapper is None:
mapper = self.get_mapper()
already_use_ports = [int(i) for i in mapper.keys()]
avail_ports = sorted(list(set(self.all_avail_ports) - set(already_use_ports)))
if len(avail_ports) <= 0:
msg = _('No ports can be used, check and modify the limit on the number '
'of ports that Magnus listens on in the configuration file.')
tips = _('All available port count: {}, Already use port count: {}').format(
len(self.all_avail_ports), len(already_use_ports)
)
error = msg + tips
raise JMSException(error)
port = avail_ports[0]
logger.debug('Get next available port: {}'.format(port))
return port
def get_already_use_ports(self):
mapper = self.get_mapper()
return sorted([int(i) for i in mapper.keys()])
@staticmethod
def oracle_ports_setting_changed():
oracle_ports_cache = cache.get('MAGNUS_ORACLE_PORTS') or ''
if settings.MAGNUS_ORACLE_PORTS.split('-')[0] != oracle_ports_cache.split('-')[0]:
logger.info('Oracle ports setting changed')
return True
return False
def get_mapper(self):
mapper = cache.get(self.CACHE_KEY, {})
if not mapper or self.oracle_ports_setting_changed():
# redis 可能被清空,重新初始化一下
mapper = self.init()
return mapper
def set_mapper(self, value):
"""
value: {
port: db_id
}
"""
cache.set(self.CACHE_KEY, value, timeout=None)
cache.set('MAGNUS_ORACLE_PORTS', settings.MAGNUS_ORACLE_PORTS)
db_port_manager = DBPortManager()