2. 요구사항 1
• 요청 메시지의 A옵션이 1인 클라이언트C1과 B옵
션이 2인 클라이언트 C2는 서브넷S1의 IP 대역에
서 네트워크 설정(IP 포함)을 할당받는다.
• 맥주소가 M3인 클라이언트C3와 M4인 클라이언
트 C4는 서브넷S2의 IP 대역 중에서 앞 쪽 절반에
서 네트워크 설정을 할당받는다.
• 요청 메시지의 E옵션이 5인 클라이언트C5와 맥주
소가 M6인 클라이언트C6은 서브넷S2의 IP 대역
중에서 뒤 쪽 절반에서 네트워크 설정을 할당받는
다.
• 맥주소 일치가 우선한다.
• 임대 IP는 클라이언트 단위로 관리된다.
2
3. 그림으로 보면
C1
C2
C3
(M3)
C4
(M4)
C5
IP
대역
S1
IP
대역
S2
A=1
B=1
C6
(M6)
E=1
3
4. 그림에서 도출
C1
C2
C3
(M3)
C4
(M4)
C5
IP
대역
S1
IP
대역
S2
A=1
B=1
C6
(M6)
E=1
옵션으로
클라이언트
구분
맥주소로
클라이언트
구분
다른 조건을 따르는 클라이언트들이
한 대역으로 묶임
한 대역을
나눠 쓸 수
있음
4
5. 요구사항 2
• 응답 옵션
– IP 대역 별로 응답 옵션을 다르게 설정
– IP 풀 별로 응답 옵션을 다르게 설정
– 클라이언트 그룹(클래스) 별로 응답 옵션을 다르
게 설정
– 특정 조건을 충족하는 클라이언트들 별로 응답 옵
션을 다르게 설정
– 같은 응답 옵션이 존재할 경우 우선 순위
• 클라이언트별 > 클라이언트 그룹 > IP 풀 > 대역
• 네트워크 설정(DNS, GW, 서브넷마스크)
– IP 대역 별로 네트워크 설정
– IP 풀 별로 다른 네트워크 설정
• IP 풀에 네트워크 설정이 있을 경우, 우선 적용
5
9. Optional 써 봄
9
public
interface
ClientFinder
{
OpFonal<?
extends
Client>
find(DhcpMessage
message);
}
public
interface
Client
{
public
OpFonal<ClientClass>
getClientClass();
...
}
public
class
ClientClass
{
private
Pool
pool;
public
OpFonal<Pool>
getPool()
{
return
OpFonal.ofNullable(pool);
}
...
}
10. 사용 가능 IP 범위 찾기
public
class
UsableIpRangeFinderImpl
implements
UsableIpRangeFinder
{
private
ClientFinder
clientFinder;
@TransacFonal
@Override
public
RangeResult
find(DhcpMessage
message)
{
if
(message
==
null)
throw
new
IllegalArgumentExcepFon();
OpFonal<?
extends
Client>
clientOpt
=
clientFinder.find(message);
OpFonal<Pool>
poolOpt
=
clientOpt.flatMap(c
-‐>
c.getClientClass()).flatMap(cc
-‐>
cc.getPool());
if
(!poolOpt.isPresent())
return
emptyResult();
Pool
pool
=
poolOpt.get();
return
new
RangeResult(
pool.getIpRange(),
getNetworkConfig(pool),
getMergedOpFons(clientOpt.get()));
}
private
NetworkConfig
getNetworkConfig(Pool
pool)
{
NetworkConfig
nc
=
pool.getNetworkConfig();
return
nc
!=
null
?
nc
:
pool.getSubnect().getNetworkConfig();
}
private
DhcpOpFons
getMergedOpFons(Client
client)
{
...
//
다음 장에 코드 표시
}
10
//
OpFonal
대신 null
사용 경우
Client
client
=
clientFinder.find(message);
if
(client
==
null)
return
emptyResult();
if
(client.getClientClass()
==
null)
return
emptyResult();
Pool
pool
=
client.getClientClass().getPool();
if
(pool
==
null)
return
emptyResult();
11. 응답 옵션 생성 부분
11
//
UsableIpRangeFinderImpl
클래스
private
DhcpOpFons
getMergedOpFons(Client
client)
{
DhcpOpFons
cOpFons
=
client.getDhcpOpFons();
ClientClass
clientClass
=
client.getClientClass().get();
DhcpOpFons
ccOpFons
=
clientClass.getDhcpOpFons();
Pool
pool
=
clientClass.getPool().get();
DhcpOpFons
poolOpFons
=
pool.getDhcpOpFons();
DhcpOpFons
subnetOpFons
=
pool.getSubnect().getDhcpOpFons();
return
subnetOp9ons.merge(poolOpFons).merge(ccOpFons).merge(cOpFons);
}
//
DhcpOpFons
클래스
public
DhcpOpFons
merge(DhcpOpFons
other)
{
DhcpOpFons
newOpFons
=
new
DhcpOpFons(this.opFonMap);
if
(other
==
null
||
other.isEmpty())
return
newOpFons;
other.opFonMap.values().forEach(ov
-‐>
newOpFons.add(ov));
return
newOpFons;
}
12. 클라이언트 찾기
• 두 종류의 클라이언트 매칭
12
public
class
HardwareAddressClient
implements
Client
{
private
HardwareAddress
hardwareAddress;
...
}
public
class
MatchClient
implements
Client
{
private
List<MatchPredicate>
predicates
=
new
ArrayList<>();
public
boolean
match(DhcpMessage
message)
{
return
predicates.stream()
.allMatch(p
-‐>
p.match(message));
}
...
}
1. 하드웨어 주소
2.
조건 일치
13. 클라이언트 찾기
13
public
class
ClientFinderImpl
implements
ClientFinder
{
private
HardwareAddressClientRepository
hardwareAddressClientRepository;
private
MatchClientRepository
matchClientRepository;
@Override
public
OpFonal<?
extends
Client>
find(DhcpMessage
message)
{
if
(message
==
null)
return
OpFonal.empty();
//
맥주소 일치가 먼저
OpFonal<HardwareAddressClient>
hwAddrClient
=
hardwareAddressClientRepository.findByHardwareAddress(message.getChaddr());
if
(hwAddrClient.isPresent())
return
hwAddrClient;
// 맥주소 일치 없으면, 조건 충족하는 Client
검색
List<MatchClient>
clients
=
matchClientRepository.findAll();
for
(MatchClient
mclient
:
clients)
if
(mclient.match(message))
return
OpFonal.of(mclient);
return
OpFonal.empty();
}
...
15. JPA 적용
• DB 연동은 JPA를 사용하기로 결정
– 기본 데이터 타입은 단순 매핑 설정 사용
– InetAddress 등에 커스텀 변환기 사용
• 적용하기 위한 몇 가지 코드 변경
– DhcpOptions 필드 à List<DhcpOption>
– MatchPredicate 계층 à 단일 클래스
15
17. 커스텀 변환기
• 값 타입과 DB 한 개 컬럼 간의 변환 위함
17
@Entity @Table(name = "SUBNET_CONFIG")
public class SubnetConfig {
@Id @Column(name = "ID")
private Long id;
@Column(name = "SUBNET")
@Convert(converter = SubnetConverter.class)
private Subnet subnet;
...
}
public class Subnet {
private InetAddress networkAddress;
private int bits;
...
}
SUBNET_CONFIG
-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐
ID:
int
SUBNET:
VARCHAR
SUBNETMASK:
VARCHAR
GATEWAY:
VARCHAR
DNSLIST:
VARCHAR
Java
new
Subnet("192.168.0.1",
24)
컬럼 값
192.168.0.1/24
18. 커스텀 컨버터 구현 예
18
@Converter
public class SubnetConverter implements AttributeConverter<Subnet, String> {
@Override
public String convertToDatabaseColumn(Subnet subnet) {
if (subnet == null) return null;
return subnet.toString();
}
@Override
public Subnet convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.isEmpty()) return null;
return new Subnet(dbData);
}
}
19. 커스텀 컨버터 구현 예
19
// List<InetAddress>와 "123.0.2.1,192.168.0.254" DB 데이터 간 변환 처리
@Converter
public class IpListConverter implements AttributeConverter<IpList, String> {
@Override
public String convertToDatabaseColumn(IpList attribute) {
if (attribute == null || attribute.isEmpty()) return "";
else return attribute.toString();
}
@Override
public IpList convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
String[] ips = dbData.split(",");
List<InetAddress> addresses = new ArrayList<>();
for (String ip : ips) {
try {
addresses.add(InetAddress.getByName(ip));
} catch (UnknownHostException e) {
throw new RuntimeException(....생략, e);
}
}
return new IpList(addresses);
}
}
20. JPA 적용 과정에서의 모델 변화
• DhcpOptions 구현 변경
– 모델과 DB간 불일치
20
@Entity
@Table(name = "CLIENT_MATCH")
public class MatchClient implements Client {
// @Embedded ??
private DhcpOptions options;
public DhcpOptions getDhcpOptions() {
return options;
}
...
}
// @Embeddable ??
public class DhcpOptions {
// ??
private Map<DhcpOption, OptionValue> optionMap;
}
CM_DHCP_OPTIONS
-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐
CM_ID:
int
LIST_INDEX:
int
OPTION_CODE:
int
OPTION_VALUE:
String
키
vs
인덱스
관리 화면에서
입력한 순서대로
보여줄 필요
21. JPA 적용 과정에서의 모델 변화
• DhcpOptions 구현 변경
– 모델과 DB간 불일치
21
@Entity @Table(name = "CLIENT_MATCH")
public class MatchClient implements Client {
private DhcpOptions options;
public DhcpOptions getDhcpOptions() {
return options;
}
...
}
@ElementCollection
@CollectionTable(name = "CLIENT_MATCH_DHCP_OPTION",
joinColumns = @JoinColumn(name = "CLIENT_MATCH_ID"))
@OrderColumn(name = "LIST_INDEX")
private List<OptionValue> options = new ArrayList<>();
public DhcpOptions getDhcpOptions() { // 메서드 시그너쳐 유지
return new DhcpOptions(options);
}
CM_DHCP_OPTIONS
-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐
CM_ID:
int
LIST_INDEX:
int
OPTION_CODE:
int
OPTION_VALUE:
String
22. JPA 적용 과정에서의 모델 변화
• MatchClient 구현 변경
– @Embeddable 타입의 상속 지원하지 않음
– 계층을 단일 클래스로 변경
22