Skip to content

Commit 6d4dfcc

Browse files
Xichen96saksarav-nokia
authored andcommitted
[dhcp_server] add config dhcp server option (sonic-net#18013)
* add dhcp server option cli
1 parent 41acac1 commit 6d4dfcc

File tree

5 files changed

+259
-9
lines changed

5 files changed

+259
-9
lines changed

dockers/docker-dhcp-server/cli-plugin-tests/mock_config_db.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@
3333
"lease_time": "3600",
3434
"mode": "PORT",
3535
"netmask": "255.255.255.0",
36-
"customized_options": "option60",
3736
"state": "disabled"
3837
},
3938
"DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|option60": {
40-
"id": "60",
39+
"id": "163",
40+
"type": "string",
41+
"value": "dummy_value"
42+
},
43+
"DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|option61": {
44+
"id": "164",
4145
"type": "string",
4246
"value": "dummy_value"
4347
},

dockers/docker-dhcp-server/cli-plugin-tests/test_config_dhcp_server.py

+150
Original file line numberDiff line numberDiff line change
@@ -606,3 +606,153 @@ def test_config_dhcp_server_ipv4_unbind_unbind_ip(self, mock_db):
606606
["Vlan100", "Ethernet4", "100.1.1.13,100.1.1.14"], obj=db)
607607
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
608608

609+
def test_config_dhcp_server_ipv4_option_add(self, mock_db):
610+
expected_value = {
611+
"option_id": "165",
612+
"type": "string",
613+
"value": "dummy_value"
614+
}
615+
runner = CliRunner()
616+
db = clicommon.Db()
617+
db.db = mock_db
618+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["add"], \
619+
["option62", "165", "string", "dummy_value"], obj=db)
620+
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
621+
assert mock_db.get_all("CONFIG_DB", "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|option62") == expected_value
622+
623+
def test_config_dhcp_server_ipv4_option_add_existing(self, mock_db):
624+
runner = CliRunner()
625+
db = clicommon.Db()
626+
db.db = mock_db
627+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["add"], \
628+
["option60", "163", "string", "dummy_value"], obj=db)
629+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
630+
631+
def test_config_dhcp_server_ipv4_option_add_illegal_option_id(self, mock_db):
632+
runner = CliRunner()
633+
db = clicommon.Db()
634+
db.db = mock_db
635+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["add"], \
636+
["option62", "10", "string", "dummy_value"], obj=db)
637+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
638+
639+
def test_config_dhcp_server_ipv4_option_add_illegal_type(self, mock_db):
640+
runner = CliRunner()
641+
db = clicommon.Db()
642+
db.db = mock_db
643+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["add"], \
644+
["option62", "165", "xx", "xx"], obj=db)
645+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
646+
647+
def test_config_dhcp_server_ipv4_option_add_illegal_value(self, mock_db):
648+
runner = CliRunner()
649+
db = clicommon.Db()
650+
db.db = mock_db
651+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["add"], \
652+
["option62", "165", "uint8", "1000000"], obj=db)
653+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
654+
655+
def test_config_dhcp_server_ipv4_option_del(self, mock_db):
656+
runner = CliRunner()
657+
db = clicommon.Db()
658+
db.db = mock_db
659+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["del"], \
660+
["option61"], obj=db)
661+
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
662+
assert mock_db.exists("CONFIG_DB", "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|option61") == False
663+
664+
def test_config_dhcp_server_ipv4_option_del_nonexisting(self, mock_db):
665+
runner = CliRunner()
666+
db = clicommon.Db()
667+
db.db = mock_db
668+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["del"], \
669+
["option62"], obj=db)
670+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
671+
672+
def test_config_dhcp_server_ipv4_option_del_referenced(self, mock_db):
673+
runner = CliRunner()
674+
db = clicommon.Db()
675+
db.db = mock_db
676+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["del"], \
677+
["option60"], obj=db)
678+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
679+
680+
def test_config_dhcp_server_ipv4_option_bind(self, mock_db):
681+
runner = CliRunner()
682+
db = clicommon.Db()
683+
db.db = mock_db
684+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["bind"], \
685+
["Vlan300", "option60"], obj=db)
686+
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
687+
assert mock_db.get("CONFIG_DB", "DHCP_SERVER_IPV4|Vlan300", "customized_options") == "option60"
688+
689+
def test_config_dhcp_server_ipv4_option_bind_multiple_options(self, mock_db):
690+
runner = CliRunner()
691+
db = clicommon.Db()
692+
db.db = mock_db
693+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["bind"], \
694+
["Vlan300", "option60,option61"], obj=db)
695+
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
696+
result = mock_db.get("CONFIG_DB", "DHCP_SERVER_IPV4|Vlan300", "customized_options")
697+
assert result and set(result.split(",")) == set("option60,option61".split(","))
698+
699+
def test_config_dhcp_server_ipv4_option_bind_to_existing(self, mock_db):
700+
runner = CliRunner()
701+
db = clicommon.Db()
702+
db.db = mock_db
703+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["bind"], \
704+
["Vlan100", "option61"], obj=db)
705+
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
706+
result = mock_db.get("CONFIG_DB", "DHCP_SERVER_IPV4|Vlan100", "customized_options")
707+
assert result and set(result.split(",")) == set("option60,option61".split(","))
708+
709+
def test_config_dhcp_server_ipv4_option_bind_same_option_to_existing(self, mock_db):
710+
runner = CliRunner()
711+
db = clicommon.Db()
712+
db.db = mock_db
713+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["bind"], \
714+
["Vlan100", "option60"], obj=db)
715+
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
716+
assert mock_db.get("CONFIG_DB", "DHCP_SERVER_IPV4|Vlan100", "customized_options") == "option60"
717+
718+
def test_config_dhcp_server_ipv4_option_bind_to_nonexisting_intf(self, mock_db):
719+
runner = CliRunner()
720+
db = clicommon.Db()
721+
db.db = mock_db
722+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["bind"], \
723+
["Vlan200", "option60"], obj=db)
724+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
725+
726+
def test_config_dhcp_server_ipv4_option_bind_nonexisting_option(self, mock_db):
727+
runner = CliRunner()
728+
db = clicommon.Db()
729+
db.db = mock_db
730+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["bind"], \
731+
["Vlan300", "option62"], obj=db)
732+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
733+
734+
def test_config_dhcp_server_ipv4_option_unbind(self, mock_db):
735+
runner = CliRunner()
736+
db = clicommon.Db()
737+
db.db = mock_db
738+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["unbind"], \
739+
["Vlan100", "option60"], obj=db)
740+
assert result.exit_code == 0, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
741+
result = mock_db.get("CONFIG_DB", "DHCP_SERVER_IPV4|Vlan100", "customized_options")
742+
assert result == None or result == ""
743+
744+
def test_config_dhcp_server_ipv4_option_unbind_nonexisting_intf(self, mock_db):
745+
runner = CliRunner()
746+
db = clicommon.Db()
747+
db.db = mock_db
748+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["unbind"], \
749+
["Vlan200", "option60"], obj=db)
750+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)
751+
752+
def test_config_dhcp_server_ipv4_option_unbind_nonexisting_option(self, mock_db):
753+
runner = CliRunner()
754+
db = clicommon.Db()
755+
db.db = mock_db
756+
result = runner.invoke(dhcp_server.dhcp_server.commands["ipv4"].commands["option"].commands["unbind"], \
757+
["Vlan100", "option61"], obj=db)
758+
assert result.exit_code == 2, "exit code: {}, Exception: {}, Traceback: {}".format(result.exit_code, result.exception, result.exc_info)

dockers/docker-dhcp-server/cli-plugin-tests/test_show_dhcp_server.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ def test_show_dhcp_server_ipv4_option_without_name(self, mock_db):
185185
+---------------+-------------+-------------+--------+
186186
| Option Name | Option ID | Value | Type |
187187
+===============+=============+=============+========+
188-
| option60 | 60 | dummy_value | string |
188+
| option60 | 163 | dummy_value | string |
189+
+---------------+-------------+-------------+--------+
190+
| option61 | 164 | dummy_value | string |
189191
+---------------+-------------+-------------+--------+
190192
"""
191193
runner = CliRunner()
@@ -200,7 +202,7 @@ def test_show_dhcp_server_ipv4_option_with_name(self, mock_db):
200202
+---------------+-------------+-------------+--------+
201203
| Option Name | Option ID | Value | Type |
202204
+===============+=============+=============+========+
203-
| option60 | 60 | dummy_value | string |
205+
| option60 | 163 | dummy_value | string |
204206
+---------------+-------------+-------------+--------+
205207
"""
206208
runner = CliRunner()

dockers/docker-dhcp-server/cli/config/plugins/dhcp_server.py

+98-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import string
66

77

8-
SUPPORT_TYPE = ["binary", "boolean", "ipv4-address", "string", "uint8", "uint16", "uint32"]
8+
SUPPORTED_TYPE = ["binary", "boolean", "ipv4-address", "string", "uint8", "uint16", "uint32"]
99

1010

1111
def validate_str_type(type_, value):
@@ -20,7 +20,7 @@ def validate_str_type(type_, value):
2020
"""
2121
if not isinstance(value, str):
2222
return False
23-
if type_ not in SUPPORT_TYPE:
23+
if type_ not in SUPPORTED_TYPE:
2424
return False
2525
if type_ == "string":
2626
return True
@@ -46,6 +46,7 @@ def validate_str_type(type_, value):
4646

4747

4848
@click.group(cls=clicommon.AbbreviationGroup, name="dhcp_server")
49+
@clicommon.pass_db
4950
def dhcp_server():
5051
"""config DHCP Server information"""
5152
ctx = click.get_current_context()
@@ -341,10 +342,103 @@ def dhcp_server_ipv4_ip_unbind(db, dhcp_interface, member_interface, range_, ip_
341342
ctx.fail("Attempting to unbind range or ip that is not binded")
342343

343344

344-
def register(cli):
345-
# cli.add_command(dhcp_server)
345+
@dhcp_server_ipv4.group(cls=clicommon.AliasedGroup, name="option")
346+
def dhcp_server_ipv4_option():
346347
pass
347348

348349

350+
SUPPORTED_OPTION_ID = ["147", "148", "149", "163", "164", "165", "166", "167", "168", "169", "170", "171", "172", "173", "174", "178", "179", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", "200", "201", "202", "203", "204", "205", "206", "207", "214", "215", "216", "217", "218", "219", "222", "223"]
351+
352+
353+
@dhcp_server_ipv4_option.command(name="add")
354+
@click.argument("option_name", required=True)
355+
@click.argument("option_id", required=True)
356+
@click.argument("type_", required=True)
357+
@click.argument("value", required=True)
358+
@clicommon.pass_db
359+
def dhcp_server_ipv4_option_add(db, option_name, option_id, type_, value):
360+
ctx = click.get_current_context()
361+
if option_id not in SUPPORTED_OPTION_ID:
362+
ctx.fail("Option id {} is not supported".format(option_id))
363+
if type_ not in SUPPORTED_TYPE:
364+
ctx.fail("Input type is not supported")
365+
if not validate_str_type(type_, value):
366+
ctx.fail("Value {} is not of type {}".format(value, type_))
367+
dbconn = db.db
368+
key = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|" + option_name
369+
if dbconn.exists("CONFIG_DB", key):
370+
ctx.fail("Option {} already exist".format(option_name))
371+
dbconn.hmset("CONFIG_DB", key, {
372+
"option_id": option_id,
373+
"type": type_,
374+
"value": value,
375+
})
376+
377+
378+
@dhcp_server_ipv4_option.command(name="del")
379+
@click.argument("option_name", required=True)
380+
@clicommon.pass_db
381+
def dhcp_server_ipv4_option_del(db, option_name):
382+
ctx = click.get_current_context()
383+
dbconn = db.db
384+
option_key = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|" + option_name
385+
if not dbconn.exists("CONFIG_DB", option_key):
386+
ctx.fail("Option {} does not exist, cannot delete".format(option_name))
387+
for key in dbconn.keys("CONFIG_DB", "DHCP_SERVER_IPV4|*"):
388+
existing_options = dbconn.get("CONFIG_DB", key, "customized_options")
389+
if existing_options and option_name in existing_options.split(","):
390+
ctx.fail("Option {} is referenced in {}, cannot delete".format(option_name, key[len("DHCP_SERVER_IPV4|"):]))
391+
dbconn.delete("CONFIG_DB", option_key)
392+
393+
394+
@dhcp_server_ipv4_option.command(name="bind")
395+
@click.argument("dhcp_interface", required=True)
396+
@click.argument("option_list", required=True)
397+
@clicommon.pass_db
398+
def dhcp_server_ipv4_option_bind(db, dhcp_interface, option_list):
399+
ctx = click.get_current_context()
400+
dbconn = db.db
401+
key = "DHCP_SERVER_IPV4|" + dhcp_interface
402+
if not dbconn.exists("CONFIG_DB", key):
403+
ctx.fail("Interface {} is not valid dhcp interface".format(dhcp_interface))
404+
option_list = option_list.split(",")
405+
for option_name in option_list:
406+
option_key = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|" + option_name
407+
if not dbconn.exists("CONFIG_DB", option_key):
408+
ctx.fail("Option {} does not exist, cannot bind".format(option_name))
409+
existing_value = dbconn.get("CONFIG_DB", key, "customized_options")
410+
value_set = set(existing_value.split(",")) if existing_value else set()
411+
new_value_set = value_set.union(option_list)
412+
dbconn.set("CONFIG_DB", key, "customized_options", ",".join(new_value_set))
413+
414+
415+
@dhcp_server_ipv4_option.command(name="unbind")
416+
@click.argument("dhcp_interface", required=True)
417+
@click.argument("option_list", required=False)
418+
@click.option("--all", "all_", required=False, default=False, is_flag=True)
419+
@clicommon.pass_db
420+
def dhcp_server_ipv4_option_unbind(db, dhcp_interface, option_list, all_):
421+
ctx = click.get_current_context()
422+
dbconn = db.db
423+
key = "DHCP_SERVER_IPV4|" + dhcp_interface
424+
if not dbconn.exists("CONFIG_DB", key):
425+
ctx.fail("Interface {} is not valid dhcp interface".format(dhcp_interface))
426+
if all_:
427+
dbconn.set("CONFIG_DB", key, "customized_options", "")
428+
else:
429+
unbind_value = set(option_list.split(","))
430+
existing_value = dbconn.get("CONFIG_DB", key, "customized_options")
431+
value_set = set(existing_value.split(",")) if existing_value else set()
432+
if value_set.issuperset(unbind_value):
433+
new_value_set = value_set.difference(unbind_value)
434+
dbconn.set("CONFIG_DB", key, "customized_options", ",".join(new_value_set))
435+
else:
436+
ctx.fail("Attempting to unbind option that is not binded")
437+
438+
439+
def register(cli):
440+
cli.add_command(dhcp_server)
441+
442+
349443
if __name__ == '__main__':
350444
dhcp_server()

dockers/docker-dhcp-server/cli/show/plugins/show_dhcp_server.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def ts_to_str(ts):
1313
return datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%d %H:%M:%S")
1414

1515

16-
@click.group(cls=clicommon.AliasedGroup)
16+
@click.group(cls=clicommon.AbbreviationGroup, name="dhcp_server")
1717
@clicommon.pass_db
1818
def dhcp_server(db):
1919
"""Show dhcp_server related info"""

0 commit comments

Comments
 (0)