Added ability to set IP's of workload hosts and added a filter to ensure the IP addresses are set

This commit is contained in:
2025-06-29 09:27:30 +09:30
parent 7a6083f308
commit d5126aafcf
4 changed files with 101 additions and 11 deletions
@@ -8,7 +8,7 @@ from app.models.network import Network, NetworkPort
from datetime import datetime
from app.controller import api_bp
import uuid
from app.scheduling_filters import ExcludeAllDisabledHosts, MostAvailableCapacity, ExcludeAllOfflineHosts, DockerCapableHosts
from app.scheduling_filters import ExcludeAllDisabledHosts, MostAvailableCapacity, ExcludeAllOfflineHosts, DockerCapableHosts, ExcludeHostsWithoutNorthSouthIP
from app.services.cloudflare import CloudflareTunnelManager
from werkzeug.exceptions import abort
from sqlalchemy import or_
@@ -168,7 +168,7 @@ def add_container_workload():
all_workload_hosts = WorkloadHost.query.filter_by(region_id=request_vdc.region.id).all()
logger.debug(f"Found {len(all_workload_hosts)} hosts eligible for placement")
filters = [DockerCapableHosts(), ExcludeAllOfflineHosts(), ExcludeAllDisabledHosts(), MostAvailableCapacity()]
filters = [ExcludeHostsWithoutNorthSouthIP(), DockerCapableHosts(), ExcludeAllOfflineHosts(), ExcludeAllDisabledHosts(), MostAvailableCapacity()]
filtered_hosts = all_workload_hosts
for filter in filters:
filtered_hosts = filter.apply(filtered_hosts)
@@ -252,7 +252,7 @@ def add_container_workload():
selected_host.id, preferred_port=preferred_external_port
)
logger.info(f"Assigning internal {internal_port} -> external {assigned_external_port} for container {new_container.id}")
logger.info(f"Assigning internal port:{internal_port} -> external port:{assigned_external_port} for container {new_container.id}")
# Add this port to NSController launch params
nscontroller_launch_params['ports'].append({
@@ -662,10 +662,12 @@ def get_container_workloads():
@api_bp.route('/workloads/pods', methods=['GET'])
def get_pods():
pods = ContainerPod.query.all()
# pods = ContainerPod.query.all()
active_pods = ContainerPod.query_with_only_active_containers().all()
response = []
for pod in pods:
for pod in active_pods:
# Get associated tunnel (if exists)
tunnel = CloudflareTunnel.query.filter_by(
nscontroller_workload_id=pod.nscontroller_workload_id,
+15 -3
View File
@@ -226,8 +226,8 @@ class WorkloadHost(BaseModel):
heartbeat_status = Column(String(50), nullable=True)
last_heartbeat_time = Column(DateTime, nullable=True)
ip_address_all = Column(LONGTEXT, nullable=True)
ip_address_eastwest = Column(String(25), nullable=True)
ip_address_northsouth = Column(String(25), nullable=True)
ip_address_eastwest = Column(String(25), nullable=True) #This IP is used for SDN capabilities in a region
ip_address_northsouth = Column(String(25), nullable=True) #This IP is used for port forwarding into a host for containers primarily
workloads = relationship(
"Workload",
@@ -686,7 +686,19 @@ class ContainerPod(BaseModel):
nscontroller_workload = relationship("Workload", foreign_keys=[nscontroller_workload_id])
container_mappings = relationship("ContainerPodContainer", back_populates="pod", cascade="all, delete-orphan")
vdc = relationship("VirtualDataCenter")
@classmethod
def query_with_only_active_containers(cls):
"""
Returns ContainerPods where all mapped containers are not soft-deleted.
"""
return (
db.session.query(cls)
.join(ContainerPodContainer, cls.id == ContainerPodContainer.pod_id)
.join(Workload, Workload.id == ContainerPodContainer.container_workload_id)
.filter(Workload.deleted == False)
.distinct()
)
class Image(BaseModel):
__tablename__ = "images"
location = Column(String(255), nullable=False)
+58 -2
View File
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from app.models.models import WorkloadHost
from logger import logger
import ipaddress
class SchedulingFilter(ABC):
@abstractmethod
def apply(self, hosts):
@@ -113,4 +113,60 @@ class MostAvailableCapacity(SchedulingFilter):
f"Input: {len(hosts)}, Output: {len(filtered_hosts)}\n"
f"Priority order: {sorted_ids}"
)
return filtered_hosts
return filtered_hosts
class ExcludeHostsWithoutNorthSouthIP(SchedulingFilter):
"""
Filter that removes every WorkloadHost lacking a valid north-south IP address.
A host is **kept** only if:
• `ip_address_northsouth` is not None / empty, **and**
• its value parses as a valid IPv4 or IPv6 address.
All others are filtered out.
"""
def apply(self, hosts):
"""
Iterate over *hosts* and return only those with a valid
`ip_address_northsouth`.
Parameters
----------
hosts : list[WorkloadHost]
Candidate hosts.
Returns
-------
list[WorkloadHost]
Hosts that passed the IP-validation check.
"""
host_ids = [str(h.id) for h in hosts]
logger.debug(
f"Starting ExcludeHostsWithoutNorthSouthIP filter. "
f"Input hosts ({len(hosts)}): {host_ids}"
)
filtered_hosts, removed_ids = [], []
for host in hosts:
ip_addr = getattr(host, "ip_address_northsouth", None)
try:
# ipaddress throws ValueError on bad input
if ip_addr and ipaddress.ip_address(ip_addr):
filtered_hosts.append(host)
else:
removed_ids.append(str(host.id))
except ValueError:
removed_ids.append(str(host.id))
filtered_ids = [str(h.id) for h in filtered_hosts]
logger.debug(
f"Finished ExcludeHostsWithoutNorthSouthIP. "
f"Input: {len(hosts)}, Filtered: {len(filtered_hosts)}\n"
f"Removed hosts: {removed_ids}\n"
f"Remaining hosts: {filtered_ids}"
)
return filtered_hosts
+21 -1
View File
@@ -142,6 +142,27 @@ def render_detail():
st.markdown(f"**Available for Scheduling:** `{workload_host['available_for_scheduling']}`")
st.markdown(f"**Placement Priority:** `{workload_host['placement_priority']}`")
st.markdown(f"**Status:** `{workload_host['status']}`")
# Network configuration section
st.subheader("Network Configuration")
# Input fields for editing East-West and North-South IPs
current_ew_ip = workload_host.get('ip_address_eastwest') or ""
current_ns_ip = workload_host.get('ip_address_northsouth') or ""
new_ew_ip = st.text_input("East-West IP Address", value=current_ew_ip)
new_ns_ip = st.text_input("North-South IP Address", value=current_ns_ip)
if st.button("Save Network Configuration"):
update_payload = {
"ip_address_eastwest": new_ew_ip,
"ip_address_northsouth": new_ns_ip
}
update_result = st.session_state.client.update_workload_host(host_id, update_payload)
if update_result and update_result.get('success'):
st.success("Network configuration updated.")
st.rerun()
else:
st.error("Failed to update network configuration.")
# Display OVS Bridges
st.subheader("Attached OVS Bridges")
@@ -152,7 +173,6 @@ def render_detail():
else:
st.info("No OVS bridges attached to this host.")
# ... (rest of the existing detail view code remains the same)
if st.session_state.view_type == 'detail' and st.session_state.selected_resource_type == 'workload_host':
render_detail()