Hierarchical Namespaces in Java: Implementing Multi-Tenant Resource Organization

Hierarchical Namespaces (HNC) extend Kubernetes namespaces to support parent-child relationships, enabling better organization and inheritance of resources across multi-tenant environments.


HNC Architecture Overview

Key Concepts:

  • Hierarchical Namespace Controller (HNC): Kubernetes operator managing namespace hierarchies
  • Parent-Child Relationships: Namespaces can have parent-child relationships
  • Resource Propagation: Policies, RBAC, and secrets can be inherited
  • Multi-tenancy: Support for complex organizational structures

Java Application → HNC → Kubernetes

  • Applications interact with hierarchical namespaces
  • HNC manages propagation and inheritance
  • Kubernetes provides the underlying namespace mechanism

Prerequisites and Dependencies

Maven Dependencies
<properties>
<spring-boot.version>3.1.0</spring-boot.version>
<kubernetes-client.version>6.7.2</kubernetes-client.version>
<fabric8-client.version>6.2.0</fabric8-client.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Kubernetes Clients -->
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>${kubernetes-client.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>${fabric8-client.version}</version>
</dependency>
<!-- HNC CRD Models -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-model</artifactId>
<version>${fabric8-client.version}</version>
</dependency>
</dependencies>
HNC Installation
# hnc-installation.yaml
apiVersion: v1
kind: Namespace
metadata:
name: hnc-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hnc-controller-manager
namespace: hnc-system
spec:
replicas: 1
selector:
matchLabels:
control-plane: hnc-controller-manager
template:
metadata:
labels:
control-plane: hnc-controller-manager
spec:
containers:
- args:
- --leader-elect
- --v=2
command:
- /manager
image: gcr.io/k8s-staging-multitenancy/hnc/controller:v1.0.0
name: manager
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 64Mi

Core HNC Java Implementation

1. HNC Configuration Properties
@ConfigurationProperties(prefix = "hnc")
@Data
public class HNCProperties {
private boolean enabled = true;
private String defaultParentNamespace = "org-root";
private boolean enablePropagation = true;
private boolean enableInheritance = true;
private ResourceInheritance resourceInheritance = new ResourceInheritance();
@Data
public static class ResourceInheritance {
private boolean configMaps = true;
private boolean secrets = true;
private boolean roles = true;
private boolean roleBindings = true;
private boolean networkPolicies = true;
private boolean resourceQuotas = true;
private boolean limitRanges = true;
}
}
2. HNC Client Service
@Service
@Slf4j
public class HNCClientService {
private final KubernetesClient kubernetesClient;
private final HNCProperties hncProperties;
public HNCClientService(HNCProperties hncProperties) {
this.hncProperties = hncProperties;
this.kubernetesClient = new DefaultKubernetesClient();
}
/**
* Create a hierarchical namespace with parent-child relationship
*/
public boolean createHierarchicalNamespace(String namespace, String parentNamespace) {
try {
// Check if parent namespace exists
if (!namespaceExists(parentNamespace)) {
log.warn("Parent namespace {} does not exist, creating it", parentNamespace);
createNamespace(parentNamespace);
}
// Create the child namespace
createNamespace(namespace);
// Create HNC subnamespace anchor
createSubnamespaceAnchor(namespace, parentNamespace);
log.info("Created hierarchical namespace: {} -> {}", parentNamespace, namespace);
return true;
} catch (Exception e) {
log.error("Failed to create hierarchical namespace {} under {}", 
namespace, parentNamespace, e);
return false;
}
}
/**
* Create a regular Kubernetes namespace
*/
private void createNamespace(String namespace) {
kubernetesClient.namespaces().createOrReplace(
new NamespaceBuilder()
.withNewMetadata()
.withName(namespace)
.addToLabels("hnc.x-k8s.io/included-namespace", "true")
.endMetadata()
.build()
);
}
/**
* Create SubnamespaceAnchor CRD for HNC
*/
private void createSubnamespaceAnchor(String namespace, String parentNamespace) {
var anchor = kubernetesClient.customResources(
getSubnamespaceAnchorContext()
).inNamespace(parentNamespace).createOrReplace(
new GenericKubernetesResourceBuilder()
.withApiVersion("hnc.x-k8s.io/v1alpha2")
.withKind("SubnamespaceAnchor")
.withNewMetadata()
.withName(namespace)
.endMetadata()
.build()
);
log.debug("Created subnamespace anchor: {}", anchor);
}
/**
* Get namespace hierarchy tree
*/
public NamespaceHierarchy getNamespaceHierarchy(String rootNamespace) {
try {
Map<String, List<String>> hierarchy = new HashMap<>();
buildHierarchy(rootNamespace, hierarchy, new HashSet<>());
return new NamespaceHierarchy(rootNamespace, hierarchy);
} catch (Exception e) {
log.error("Failed to build namespace hierarchy for {}", rootNamespace, e);
return new NamespaceHierarchy(rootNamespace, Collections.emptyMap());
}
}
private void buildHierarchy(String currentNamespace, 
Map<String, List<String>> hierarchy, 
Set<String> visited) {
if (visited.contains(currentNamespace)) {
return; // Prevent cycles
}
visited.add(currentNamespace);
List<String> children = getChildNamespaces(currentNamespace);
hierarchy.put(currentNamespace, children);
for (String child : children) {
buildHierarchy(child, hierarchy, visited);
}
}
/**
* Get all child namespaces of a parent
*/
public List<String> getChildNamespaces(String parentNamespace) {
try {
var anchors = kubernetesClient.customResources(
getSubnamespaceAnchorContext()
).inNamespace(parentNamespace).list();
return anchors.getItems().stream()
.map(anchor -> anchor.getMetadata().getName())
.collect(Collectors.toList());
} catch (Exception e) {
log.error("Failed to get child namespaces for {}", parentNamespace, e);
return Collections.emptyList();
}
}
/**
* Get parent namespace of a child
*/
public String getParentNamespace(String childNamespace) {
try {
// Look for HNC hierarchy configuration
var namespace = kubernetesClient.namespaces().withName(childNamespace).get();
if (namespace != null) {
String parent = namespace.getMetadata().getAnnotations()
.get("hnc.x-k8s.io/parent");
return parent != null ? parent : "none";
}
return "none";
} catch (Exception e) {
log.error("Failed to get parent namespace for {}", childNamespace, e);
return "unknown";
}
}
/**
* Propagate resource to child namespaces
*/
public boolean propagateResource(String sourceNamespace, String resourceType, 
String resourceName, List<String> targetNamespaces) {
try {
for (String targetNamespace : targetNamespaces) {
copyResource(sourceNamespace, targetNamespace, resourceType, resourceName);
}
log.info("Propagated {} {} from {} to {} namespaces", 
resourceType, resourceName, sourceNamespace, targetNamespaces.size());
return true;
} catch (Exception e) {
log.error("Failed to propagate resource {}:{} from {}", 
resourceType, resourceName, sourceNamespace, e);
return false;
}
}
private void copyResource(String sourceNamespace, String targetNamespace, 
String resourceType, String resourceName) {
switch (resourceType.toLowerCase()) {
case "configmap":
copyConfigMap(sourceNamespace, targetNamespace, resourceName);
break;
case "secret":
copySecret(sourceNamespace, targetNamespace, resourceName);
break;
case "role":
copyRole(sourceNamespace, targetNamespace, resourceName);
break;
default:
throw new IllegalArgumentException("Unsupported resource type: " + resourceType);
}
}
private void copyConfigMap(String sourceNamespace, String targetNamespace, String configMapName) {
var configMap = kubernetesClient.configMaps()
.inNamespace(sourceNamespace)
.withName(configMapName)
.get();
if (configMap != null) {
kubernetesClient.configMaps()
.inNamespace(targetNamespace)
.createOrReplace(
new ConfigMapBuilder(configMap)
.editMetadata()
.withNamespace(targetNamespace)
.withResourceVersion(null)
.endMetadata()
.build()
);
}
}
private boolean namespaceExists(String namespace) {
return kubernetesClient.namespaces().withName(namespace).get() != null;
}
private CustomResourceDefinitionContext getSubnamespaceAnchorContext() {
return new CustomResourceDefinitionContext.Builder()
.withName("subnamespaceanchors.hnc.x-k8s.io")
.withGroup("hnc.x-k8s.io")
.withVersion("v1alpha2")
.withPlural("subnamespaceanchors")
.withScope("Namespaced")
.build();
}
}
3. Namespace Hierarchy Model
@Data
@AllArgsConstructor
public class NamespaceHierarchy {
private String rootNamespace;
private Map<String, List<String>> children;
private Instant createdAt;
public NamespaceHierarchy(String rootNamespace, Map<String, List<String>> children) {
this.rootNamespace = rootNamespace;
this.children = children;
this.createdAt = Instant.now();
}
public List<String> getAllDescendants(String namespace) {
List<String> descendants = new ArrayList<>();
collectDescendants(namespace, descendants);
return descendants;
}
private void collectDescendants(String namespace, List<String> descendants) {
List<String> children = this.children.getOrDefault(namespace, Collections.emptyList());
descendants.addAll(children);
for (String child : children) {
collectDescendants(child, descendants);
}
}
public int getDepth(String namespace) {
return calculateDepth(namespace, rootNamespace, 0);
}
private int calculateDepth(String targetNamespace, String currentNamespace, int currentDepth) {
if (targetNamespace.equals(currentNamespace)) {
return currentDepth;
}
List<String> children = this.children.getOrDefault(currentNamespace, Collections.emptyList());
for (String child : children) {
int depth = calculateDepth(targetNamespace, child, currentDepth + 1);
if (depth != -1) {
return depth;
}
}
return -1; // Not found
}
}
@Data
public class TenantNamespace {
private String name;
private String parent;
private String tenantId;
private String environment;
private Map<String, String> labels;
private Map<String, String> annotations;
private Instant createdAt;
private NamespaceStatus status;
public enum NamespaceStatus {
CREATING, ACTIVE, SUSPENDED, DELETING
}
}

Multi-Tenant Service Implementation

1. Tenant Management Service
@Service
@Slf4j
public class TenantManagementService {
private final HNCClientService hncClient;
private final HNCProperties hncProperties;
private final KubernetesClient kubernetesClient;
public TenantManagementService(HNCClientService hncClient, HNCProperties hncProperties) {
this.hncClient = hncClient;
this.hncProperties = hncProperties;
this.kubernetesClient = new DefaultKubernetesClient();
}
/**
* Create a complete tenant hierarchy
*/
public TenantNamespace createTenantHierarchy(String tenantId, String tenantName) {
try {
// Create tenant root namespace
String tenantRootNs = "tenant-" + tenantId;
hncClient.createHierarchicalNamespace(tenantRootNs, hncProperties.getDefaultParentNamespace());
// Create environment namespaces
String devNamespace = tenantRootNs + "-dev";
String stagingNamespace = tenantRootNs + "-staging";
String productionNamespace = tenantRootNs + "-prod";
hncClient.createHierarchicalNamespace(devNamespace, tenantRootNs);
hncClient.createHierarchicalNamespace(stagingNamespace, tenantRootNs);
hncClient.createHierarchicalNamespace(productionNamespace, tenantRootNs);
// Apply tenant-specific configurations
applyTenantConfigurations(tenantRootNs, tenantId, tenantName);
log.info("Created tenant hierarchy for {}: {}", tenantId, tenantRootNs);
return buildTenantNamespace(tenantRootNs, tenantId, tenantName);
} catch (Exception e) {
log.error("Failed to create tenant hierarchy for {}", tenantId, e);
throw new TenantCreationException("Failed to create tenant: " + tenantId, e);
}
}
/**
* Apply tenant-specific configurations and policies
*/
private void applyTenantConfigurations(String tenantRootNs, String tenantId, String tenantName) {
// Create tenant-specific Role
createTenantRole(tenantRootNs, tenantId);
// Create tenant-specific ResourceQuota
createTenantResourceQuota(tenantRootNs, tenantId);
// Create tenant-specific NetworkPolicy
createTenantNetworkPolicy(tenantRootNs, tenantId);
// Create tenant configuration ConfigMap
createTenantConfigMap(tenantRootNs, tenantId, tenantName);
}
private void createTenantRole(String namespace, String tenantId) {
var role = new RoleBuilder()
.withNewMetadata()
.withName(tenantId + "-admin")
.withNamespace(namespace)
.addToLabels("tenant", tenantId)
.endMetadata()
.withRules(
new PolicyRuleBuilder()
.withApiGroups("")
.withResources("pods", "services", "configmaps", "secrets")
.withVerbs("get", "list", "watch", "create", "update", "delete")
.build(),
new PolicyRuleBuilder()
.withApiGroups("apps")
.withResources("deployments", "statefulsets", "replicasets")
.withVerbs("get", "list", "watch", "create", "update", "delete")
.build()
)
.build();
kubernetesClient.rbac().roles().inNamespace(namespace).createOrReplace(role);
}
private void createTenantResourceQuota(String namespace, String tenantId) {
var resourceQuota = new ResourceQuotaBuilder()
.withNewMetadata()
.withName(tenantId + "-quota")
.withNamespace(namespace)
.addToLabels("tenant", tenantId)
.endMetadata()
.withNewSpec()
.withHard(Map.of(
"requests.cpu", "2",
"requests.memory", "4Gi",
"limits.cpu", "4",
"limits.memory", "8Gi",
"pods", "50",
"services", "20"
))
.endSpec()
.build();
kubernetesClient.resourceQuotas().inNamespace(namespace).createOrReplace(resourceQuota);
}
private void createTenantConfigMap(String namespace, String tenantId, String tenantName) {
var configMap = new ConfigMapBuilder()
.withNewMetadata()
.withName(tenantId + "-config")
.withNamespace(namespace)
.addToLabels("tenant", tenantId)
.endMetadata()
.withData(Map.of(
"tenant.id", tenantId,
"tenant.name", tenantName,
"database.host", "db-" + tenantId + ".svc.cluster.local",
"cache.host", "cache-" + tenantId + ".svc.cluster.local"
))
.build();
kubernetesClient.configMaps().inNamespace(namespace).createOrReplace(configMap);
}
private void createTenantNetworkPolicy(String namespace, String tenantId) {
var networkPolicy = new NetworkPolicyBuilder()
.withNewMetadata()
.withName(tenantId + "-isolation")
.withNamespace(namespace)
.addToLabels("tenant", tenantId)
.endMetadata()
.withNewSpec()
.withPodSelector(new LabelSelector()) // All pods in namespace
.withPolicyTypes("Ingress", "Egress")
.withNewIngress()
.addNewFrom()
.withPodSelector(new LabelSelectorBuilder()
.addToMatchLabels("tenant", tenantId)
.build())
.endFrom()
.endIngress()
.withNewEgress()
.addNewTo()
.withPodSelector(new LabelSelectorBuilder()
.addToMatchLabels("tenant", tenantId)
.build())
.endTo()
.endEgress()
.endSpec()
.build();
kubernetesClient.networkPolicies().inNamespace(namespace).createOrReplace(networkPolicy);
}
/**
* Get all namespaces for a tenant
*/
public List<String> getTenantNamespaces(String tenantId) {
String tenantRootNs = "tenant-" + tenantId;
NamespaceHierarchy hierarchy = hncClient.getNamespaceHierarchy(tenantRootNs);
return hierarchy.getAllDescendants(tenantRootNs);
}
/**
* Delete tenant hierarchy
*/
public boolean deleteTenantHierarchy(String tenantId) {
try {
String tenantRootNs = "tenant-" + tenantId;
// Get all namespaces in the hierarchy
List<String> tenantNamespaces = getTenantNamespaces(tenantId);
tenantNamespaces.add(tenantRootNs);
// Delete namespaces in reverse order (children first)
Collections.reverse(tenantNamespaces);
for (String namespace : tenantNamespaces) {
kubernetesClient.namespaces().withName(namespace).delete();
log.info("Deleted tenant namespace: {}", namespace);
}
log.info("Deleted tenant hierarchy for: {}", tenantId);
return true;
} catch (Exception e) {
log.error("Failed to delete tenant hierarchy for {}", tenantId, e);
return false;
}
}
private TenantNamespace buildTenantNamespace(String namespace, String tenantId, String tenantName) {
return TenantNamespace.builder()
.name(namespace)
.parent(hncClient.getParentNamespace(namespace))
.tenantId(tenantId)
.tenantName(tenantName)
.environment(extractEnvironment(namespace))
.labels(Map.of(
"tenant", tenantId,
"environment", extractEnvironment(namespace)
))
.annotations(Map.of(
"hnc.x-k8s.io/included-namespace", "true"
))
.createdAt(Instant.now())
.status(TenantNamespace.NamespaceStatus.ACTIVE)
.build();
}
private String extractEnvironment(String namespace) {
if (namespace.endsWith("-dev")) return "development";
if (namespace.endsWith("-staging")) return "staging";
if (namespace.endsWith("-prod")) return "production";
return "root";
}
}
2. Multi-Tenant Application Service
@Service
@Slf4j
public class MultiTenantApplicationService {
private final HNCClientService hncClient;
private final TenantManagementService tenantService;
private final KubernetesClient kubernetesClient;
public MultiTenantApplicationService(HNCClientService hncClient, 
TenantManagementService tenantService) {
this.hncClient = hncClient;
this.tenantService = tenantService;
this.kubernetesClient = new DefaultKubernetesClient();
}
/**
* Deploy application to tenant namespace
*/
public boolean deployToTenant(String tenantId, String environment, 
ApplicationDeployment deployment) {
try {
String targetNamespace = determineTargetNamespace(tenantId, environment);
// Validate namespace exists
if (!hncClient.namespaceExists(targetNamespace)) {
throw new NamespaceNotFoundException(
"Target namespace not found: " + targetNamespace);
}
// Deploy application resources
deployApplicationResources(targetNamespace, deployment);
// Create service
createService(targetNamespace, deployment);
// Create ingress if needed
if (deployment.isExposePublicly()) {
createIngress(targetNamespace, deployment, tenantId);
}
log.info("Deployed application {} to tenant {} in namespace {}", 
deployment.getAppName(), tenantId, targetNamespace);
return true;
} catch (Exception e) {
log.error("Failed to deploy application to tenant {}: {}", tenantId, e.getMessage());
return false;
}
}
/**
* Deploy application to all environments of a tenant
*/
public boolean deployToAllEnvironments(String tenantId, ApplicationDeployment deployment) {
List<String> environments = List.of("dev", "staging", "prod");
boolean allSuccess = true;
for (String env : environments) {
boolean success = deployToTenant(tenantId, env, deployment);
if (!success) {
allSuccess = false;
log.warn("Failed to deploy to {} environment for tenant {}", env, tenantId);
}
}
return allSuccess;
}
private String determineTargetNamespace(String tenantId, String environment) {
return String.format("tenant-%s-%s", tenantId, environment);
}
private void deployApplicationResources(String namespace, ApplicationDeployment deployment) {
// Create Deployment
var deploymentResource = new DeploymentBuilder()
.withNewMetadata()
.withName(deployment.getAppName())
.withNamespace(namespace)
.addToLabels("app", deployment.getAppName())
.addToLabels("version", deployment.getVersion())
.endMetadata()
.withNewSpec()
.withReplicas(deployment.getReplicas())
.withNewSelector()
.addToMatchLabels("app", deployment.getAppName())
.endSelector()
.withNewTemplate()
.withNewMetadata()
.addToLabels("app", deployment.getAppName())
.addToLabels("version", deployment.getVersion())
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName(deployment.getAppName())
.withImage(deployment.getImage())
.withPorts(deployment.getContainerPorts().stream()
.map(port -> new ContainerPortBuilder()
.withContainerPort(port)
.build())
.collect(Collectors.toList()))
.withResources(deployment.getResourceRequirements())
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.build();
kubernetesClient.apps().deployments().inNamespace(namespace)
.createOrReplace(deploymentResource);
}
private void createService(String namespace, ApplicationDeployment deployment) {
var service = new ServiceBuilder()
.withNewMetadata()
.withName(deployment.getAppName() + "-service")
.withNamespace(namespace)
.endMetadata()
.withNewSpec()
.withSelector(Map.of("app", deployment.getAppName()))
.withPorts(deployment.getContainerPorts().stream()
.map(port -> new ServicePortBuilder()
.withPort(port)
.withTargetPort(new IntOrString(port))
.build())
.collect(Collectors.toList()))
.withType("ClusterIP")
.endSpec()
.build();
kubernetesClient.services().inNamespace(namespace).createOrReplace(service);
}
private void createIngress(String namespace, ApplicationDeployment deployment, String tenantId) {
var ingress = new IngressBuilder()
.withNewMetadata()
.withName(deployment.getAppName() + "-ingress")
.withNamespace(namespace)
.addToAnnotations("nginx.ingress.kubernetes.io/rewrite-target", "/")
.endMetadata()
.withNewSpec()
.addNewRule()
.withHost(String.format("%s.%s.example.com", deployment.getAppName(), tenantId))
.withNewHttp()
.addNewPath()
.withPath("/")
.withPathType("Prefix")
.withNewBackend()
.withNewService()
.withName(deployment.getAppName() + "-service")
.withNewPort()
.withNumber(deployment.getContainerPorts().get(0))
.endPort()
.endService()
.endBackend()
.endPath()
.endHttp()
.endRule()
.endSpec()
.build();
kubernetesClient.network().ingresses().inNamespace(namespace).createOrReplace(ingress);
}
/**
* Get application status across all tenant environments
*/
public Map<String, ApplicationStatus> getTenantApplicationStatus(String tenantId, String appName) {
Map<String, ApplicationStatus> statusMap = new HashMap<>();
List<String> environments = List.of("dev", "staging", "prod");
for (String env : environments) {
String namespace = determineTargetNamespace(tenantId, env);
ApplicationStatus status = getApplicationStatus(namespace, appName);
statusMap.put(env, status);
}
return statusMap;
}
private ApplicationStatus getApplicationStatus(String namespace, String appName) {
try {
var deployment = kubernetesClient.apps().deployments()
.inNamespace(namespace)
.withName(appName)
.get();
if (deployment == null) {
return ApplicationStatus.NOT_FOUND;
}
Integer availableReplicas = deployment.getStatus().getAvailableReplicas();
Integer desiredReplicas = deployment.getStatus().getReplicas();
if (availableReplicas == null || availableReplicas == 0) {
return ApplicationStatus.NOT_READY;
}
if (availableReplicas.equals(desiredReplicas)) {
return ApplicationStatus.READY;
} else {
return ApplicationStatus.SCALING;
}
} catch (Exception e) {
log.error("Failed to get application status for {} in {}", appName, namespace, e);
return ApplicationStatus.UNKNOWN;
}
}
}
@Data
@Builder
class ApplicationDeployment {
private String appName;
private String version;
private String image;
private int replicas;
private List<Integer> containerPorts;
private ResourceRequirements resourceRequirements;
private boolean exposePublicly;
private Map<String, String> environmentVariables;
}
enum ApplicationStatus {
NOT_FOUND, NOT_READY, SCALING, READY, UNKNOWN
}

REST Controllers for HNC Management

1. Tenant Management Controller
@RestController
@RequestMapping("/api/tenants")
@Slf4j
public class TenantController {
private final TenantManagementService tenantService;
private final MultiTenantApplicationService appService;
public TenantController(TenantManagementService tenantService,
MultiTenantApplicationService appService) {
this.tenantService = tenantService;
this.appService = appService;
}
@PostMapping
public ResponseEntity<TenantResponse> createTenant(@RequestBody CreateTenantRequest request) {
try {
TenantNamespace tenantNs = tenantService.createTenantHierarchy(
request.getTenantId(), request.getTenantName());
return ResponseEntity.ok(TenantResponse.success(tenantNs));
} catch (Exception e) {
log.error("Failed to create tenant: {}", request.getTenantId(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(TenantResponse.error(e.getMessage()));
}
}
@DeleteMapping("/{tenantId}")
public ResponseEntity<TenantResponse> deleteTenant(@PathVariable String tenantId) {
try {
boolean success = tenantService.deleteTenantHierarchy(tenantId);
if (success) {
return ResponseEntity.ok(TenantResponse.success("Tenant deleted successfully"));
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(TenantResponse.error("Failed to delete tenant"));
}
} catch (Exception e) {
log.error("Failed to delete tenant: {}", tenantId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(TenantResponse.error(e.getMessage()));
}
}
@GetMapping("/{tenantId}/namespaces")
public ResponseEntity<List<String>> getTenantNamespaces(@PathVariable String tenantId) {
try {
List<String> namespaces = tenantService.getTenantNamespaces(tenantId);
return ResponseEntity.ok(namespaces);
} catch (Exception e) {
log.error("Failed to get namespaces for tenant: {}", tenantId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/{tenantId}/deploy")
public ResponseEntity<DeploymentResponse> deployToTenant(
@PathVariable String tenantId,
@RequestBody DeploymentRequest request) {
try {
ApplicationDeployment deployment = ApplicationDeployment.builder()
.appName(request.getAppName())
.version(request.getVersion())
.image(request.getImage())
.replicas(request.getReplicas())
.containerPorts(request.getPorts())
.resourceRequirements(request.getResources())
.exposePublicly(request.isExposePublicly())
.build();
boolean success;
if ("all".equalsIgnoreCase(request.getEnvironment())) {
success = appService.deployToAllEnvironments(tenantId, deployment);
} else {
success = appService.deployToTenant(tenantId, request.getEnvironment(), deployment);
}
if (success) {
return ResponseEntity.ok(DeploymentResponse.success("Deployment initiated"));
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(DeploymentResponse.error("Deployment failed"));
}
} catch (Exception e) {
log.error("Failed to deploy to tenant: {}", tenantId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(DeploymentResponse.error(e.getMessage()));
}
}
@GetMapping("/{tenantId}/apps/{appName}/status")
public ResponseEntity<Map<String, ApplicationStatus>> getAppStatus(
@PathVariable String tenantId,
@PathVariable String appName) {
try {
Map<String, ApplicationStatus> status = appService
.getTenantApplicationStatus(tenantId, appName);
return ResponseEntity.ok(status);
} catch (Exception e) {
log.error("Failed to get app status for tenant {}: {}", tenantId, e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
@Data
class CreateTenantRequest {
@NotBlank
private String tenantId;
@NotBlank
private String tenantName;
private Map<String, String> labels;
private Map<String, String> annotations;
}
@Data
class DeploymentRequest {
@NotBlank
private String appName;
@NotBlank
private String version;
@NotBlank
private String image;
private String environment = "dev";
private int replicas = 1;
private List<Integer> ports = List.of(8080);
private ResourceRequirements resources;
private boolean exposePublicly = false;
}
@Data
@AllArgsConstructor
class TenantResponse {
private boolean success;
private String message;
private TenantNamespace tenantNamespace;
public static TenantResponse success(TenantNamespace tenantNamespace) {
return new TenantResponse(true, "Tenant created successfully", tenantNamespace);
}
public static TenantResponse success(String message) {
return new TenantResponse(true, message, null);
}
public static TenantResponse error(String message) {
return new TenantResponse(false, message, null);
}
}
@Data
@AllArgsConstructor
class DeploymentResponse {
private boolean success;
private String message;
public static DeploymentResponse success(String message) {
return new DeploymentResponse(true, message);
}
public static DeploymentResponse error(String message) {
return new DeploymentResponse(false, message);
}
}
2. HNC Administration Controller
@RestController
@RequestMapping("/api/hnc")
@Slf4j
public class HNCAdminController {
private final HNCClientService hncClient;
public HNCAdminController(HNCClientService hncClient) {
this.hncClient = hncClient;
}
@PostMapping("/namespaces")
public ResponseEntity<String> createHierarchicalNamespace(
@RequestBody CreateNamespaceRequest request) {
try {
boolean success = hncClient.createHierarchicalNamespace(
request.getNamespace(), request.getParentNamespace());
if (success) {
return ResponseEntity.ok("Namespace created successfully");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to create namespace");
}
} catch (Exception e) {
log.error("Failed to create hierarchical namespace", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error: " + e.getMessage());
}
}
@GetMapping("/hierarchy")
public ResponseEntity<NamespaceHierarchy> getNamespaceHierarchy(
@RequestParam(defaultValue = "org-root") String rootNamespace) {
try {
NamespaceHierarchy hierarchy = hncClient.getNamespaceHierarchy(rootNamespace);
return ResponseEntity.ok(hierarchy);
} catch (Exception e) {
log.error("Failed to get namespace hierarchy", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/namespaces/{namespace}/children")
public ResponseEntity<List<String>> getChildNamespaces(@PathVariable String namespace) {
try {
List<String> children = hncClient.getChildNamespaces(namespace);
return ResponseEntity.ok(children);
} catch (Exception e) {
log.error("Failed to get child namespaces for {}", namespace, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/propagate")
public ResponseEntity<String> propagateResource(@RequestBody PropagateRequest request) {
try {
boolean success = hncClient.propagateResource(
request.getSourceNamespace(),
request.getResourceType(),
request.getResourceName(),
request.getTargetNamespaces()
);
if (success) {
return ResponseEntity.ok("Resource propagated successfully");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to propagate resource");
}
} catch (Exception e) {
log.error("Failed to propagate resource", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error: " + e.getMessage());
}
}
}
@Data
class CreateNamespaceRequest {
@NotBlank
private String namespace;
@NotBlank
private String parentNamespace;
private Map<String, String> labels;
}
@Data
class PropagateRequest {
@NotBlank
private String sourceNamespace;
@NotBlank
private String resourceType; // configmap, secret, role
@NotBlank
private String resourceName;
@NotEmpty
private List<String> targetNamespaces;
}

Configuration and Exception Handling

1. Spring Boot Configuration
# application.yaml
spring:
application:
name: hnc-manager
hnc:
enabled: true
default-parent-namespace: org-root
enable-propagation: true
enable-inheritance: true
resource-inheritance:
config-maps: true
secrets: true
roles: true
role-bindings: true
network-policies: true
resource-quotas: true
limit-ranges: true
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
logging:
level:
com.example.hnc: DEBUG
2. Custom Exceptions
public class TenantCreationException extends RuntimeException {
public TenantCreationException(String message) {
super(message);
}
public TenantCreationException(String message, Throwable cause) {
super(message, cause);
}
}
public class NamespaceNotFoundException extends RuntimeException {
public NamespaceNotFoundException(String message) {
super(message);
}
public NamespaceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
@ControllerAdvice
public class HNCExceptionHandler {
@ExceptionHandler(TenantCreationException.class)
public ResponseEntity<ErrorResponse> handleTenantCreationException(TenantCreationException e) {
log.error("Tenant creation failed", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("TENANT_CREATION_FAILED", e.getMessage()));
}
@ExceptionHandler(NamespaceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNamespaceNotFoundException(NamespaceNotFoundException e) {
log.error("Namespace not found", e);
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("NAMESPACE_NOT_FOUND", e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
log.error("Unexpected error", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred"));
}
}
@Data
@AllArgsConstructor
class ErrorResponse {
private String code;
private String message;
private Instant timestamp;
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
this.timestamp = Instant.now();
}
}

Testing

1. Unit Tests
@ExtendWith(MockitoExtension.class)
class TenantManagementServiceTest {
@Mock
private HNCClientService hncClient;
@Mock
private KubernetesClient kubernetesClient;
@InjectMocks
private TenantManagementService tenantService;
@Test
void shouldCreateTenantHierarchy() {
// Given
String tenantId = "test-tenant";
String tenantName = "Test Tenant";
when(hncClient.namespaceExists(anyString())).thenReturn(true);
when(hncClient.getParentNamespace(anyString())).thenReturn("org-root");
// When
TenantNamespace result = tenantService.createTenantHierarchy(tenantId, tenantName);
// Then
assertThat(result).isNotNull();
assertThat(result.getTenantId()).isEqualTo(tenantId);
assertThat(result.getStatus()).isEqualTo(TenantNamespace.NamespaceStatus.ACTIVE);
}
}
@SpringBootTest
class HNCIntegrationTest {
@Autowired
private TenantManagementService tenantService;
@Test
void shouldManageTenantLifecycle() {
// Integration test with test Kubernetes cluster
// This would require a test Kubernetes environment
}
}

Best Practices

  1. Namespace Naming Conventions: Use consistent naming patterns
  2. Resource Quotas: Set appropriate limits for each tenant
  3. Security Boundaries: Use NetworkPolicies to isolate tenants
  4. Monitoring: Track namespace creation and resource usage
  5. Backup: Implement namespace backup strategies
  6. Cleanup: Automatically clean up unused tenant namespaces
// Example of automated cleanup
@Component
@Slf4j
public class NamespaceCleanupService {
private final TenantManagementService tenantService;
@Scheduled(cron = "0 0 2 * * ?") // Daily at 2 AM
public void cleanupInactiveTenants() {
log.info("Starting namespace cleanup...");
// Implementation to find and delete inactive tenants
// Based on last activity, resource usage, etc.
}
}

Conclusion

Hierarchical Namespaces in Java provide:

  • Organizational structure for complex multi-tenant environments
  • Resource inheritance across namespace hierarchies
  • Policy propagation for consistent security and configuration
  • Simplified management of large-scale Kubernetes deployments
  • Tenant isolation with proper resource boundaries

By implementing the patterns shown above, you can build robust multi-tenant applications that leverage HNC for better organization, security, and management of Kubernetes resources across different teams, projects, or customers.

Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)

https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.

https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.

https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.

https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.

https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.

https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.

https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.

https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.

https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper