Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dhcp_server] Add dhcp server cli #17105

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
358 changes: 358 additions & 0 deletions dockers/docker-dhcp-server/cli/config/plugins/dhcp_server.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,371 @@
import click
import utilities_common.cli as clicommon

import ipaddress


@click.group(cls=clicommon.AbbreviationGroup, name="dhcp_server")
def dhcp_server():
"""config DHCP Server information"""
pass


@dhcp_server.group(cls=clicommon.AliasedGroup)
def ipv4():
"""Show ipv4 related dhcp_server info"""
pass


@dhcp_server.group(cls=clicommon.AliasedGroup)
def ipv6():
Copy link
Contributor

@yaqiangz yaqiangz Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need ipv6 currently, you can remove it. I think if add ipv6 part here, it will auto generate command when press "tab"

"""Show ipv6 related dhcp_server info"""
pass


def sanity_check_add(mode, lease_time, infer_gw_nm, gateway, netmask, dhcp_interface):
if mode != "PORT":
raise Exception("Only mode PORT is supported")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be use ctx.fail instead raising exception is better?

ctx.fail("{} is invalid IP address".format(ip))

if not lease_time.isdigit():
raise Exception("lease_time is required and must be nonnegative integer")
if not inter_gw_nm:
ipaddress.ip_address(gateway)
ipaddress.ip_address(netmask)


def infer_param_for_add(dhcp_interface):
pass


@ipv4.command()
@click.argument("dhcp_interface", required=True)
@click.option("--mode", required=True)
@click.option("--lease_time", required=False, default="900")
@click.option("--infer_gw_nm", required=False, default=False, is_flag=True)
@click.option("--gateway", required=False)
@click.option("--netmask", required=False)
@clicommon.pass_db
def add(db, mode, lease_time, infer_gw_nm, gateway, netmask, dhcp_interface):
sanity_check_add(mode, lease_time, infer_gw_nm, gateway, netmask, dhcp_interface):
if infer_gw_nm:
gateway, netmask = infer_param_for_add(dhcp_interface)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add check to make sure gateway and netmask are given if infer_gw_nm is not given

dbconn = db.db
key = "DHCP_SERVER_IPV4|" + dhcp_interface
dbconn.hmset("CONFIG_DB", key, {
"mode": mode,
"lease_time": lease_time,
"gateway": gateway,
"netmask": netmask,
"customized_options": "",
Copy link
Contributor

@yaqiangz yaqiangz Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In yang model, "customized_options" field is not required. I think maybe we can delete this field if it is empty?

"state": "disabled",
})


@ipv6.command()
@click.argument("dhcp_interface", required=True)
@click.option("--mode", required=True)
@click.option("--lease_time", required=False, default="900")
@click.option("--infer_gw_nm", required=False, default=False, is_flag=True)
@click.option("--gateway", required=False)
@click.option("--netmask", required=False)
@clicommon.pass_db
def add(db, mode, lease_time, infer_gw_nm, gateway, netmask, dhcp_interface):
pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for ipv6 too



@ipv4.command()
@click.argument("dhcp_interface", required=True)
@clicommon.pass_db
def del(db, dhcp_interface):
dbconn = db.db
key = "DHCP_SERVER_IPV4|" + dhcp_interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to check whether this key exist and give related echo msg

dbconn.delete("CONFIG_DB", key)


@ipv6.command()
@click.argument("dhcp_interface", required=True)
@clicommon.pass_db
def del(db, dhcp_interface):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous

pass


@ipv4.command()
@click.argument("dhcp_interface", required=True)
@clicommon.pass_db
def enable(db, dhcp_interface):
dbconn = db.db
key = "DHCP_SERVER_IPV4|" + dhcp_interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether key and entry exist

dbconn.set("CONFIG_DB", key, "state", "enabled")


@ipv6.command()
@click.argument("dhcp_interface", required=True)
@clicommon.pass_db
def enable(db, dhcp_interface):
pass


@ipv4.command()
@click.argument("dhcp_interface", required=True)
@clicommon.pass_db
def disable(db, dhcp_interface):
dbconn = db.db
key = "DHCP_SERVER_IPV4|" + dhcp_interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether key and entry exist

dbconn.set("CONFIG_DB", key, "state", "disabled")


@ipv6.command()
@click.argument("dhcp_interface", required=True)
@clicommon.pass_db
def disable(db, dhcp_interface):
pass


@ipv4.command()
@clicommon.pass_db
def update(db):
pass


@ipv6.command()
@clicommon.pass_db
def update(db):
pass


@ipv4.group(cls=clicommon.AliasedGroup)
def range():
pass


@range.command()
@click.argument("range_name", required=True)
@click.argument("ip_start", required=True)
@click.argument("ip_end", required=False)
@clicommon.pass_db
def add(db, range_name, ip_start, ip_end):
if not ip_end:
ip_end = ip_start
ipaddress.ip_address(ip_start)
Copy link
Contributor

@yaqiangz yaqiangz Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether range_name exist (expected: not exist)

ipaddress.ip_address(ip_end)
dbconn = db.db
key = "DHCP_SERVER_IPV4_RANGE|" + range_name
db.hmset("CONFIG_DB", key, {"ranges": ip_start + "," + ip_end})


@range.command()
@click.argument("range_name", required=True)
@click.argument("ip_start", required=True)
@click.argument("ip_end", required=False)
@clicommon.pass_db
def update(db, range_name, ip_start, ip_end):
if not ip_end:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether range_name exist(expected: exist)

ip_end = ip_start
ipaddress.ip_address(ip_start)
ipaddress.ip_address(ip_end)
dbconn = db.db
key = "DHCP_SERVER_IPV4_RANGE|" + range_name
db.hmset("CONFIG_DB", key, {"ranges": ip_start + "," + ip_end})


@range.command()
@click.argument("range_name", required=True)
@clicommon.pass_db
def del(db, range_name)
dbconn = db.db
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether range_name exist(expected: exist)

key = "DHCP_SERVER_IPV4_RANGE|" + range_name
db.delete("CONFIG_DB", key)


@ipv6.group(cls=clicommon.AliasedGroup)
def range():
pass


@range.command()
@click.argument("range_name", required=True)
@click.argument("ip_start", required=True)
@click.argument("ip_end", required=False)
@clicommon.pass_db
def add(db, range_name, ip_start, ip_end):
pass


@range.command()
@click.argument("range_name", required=True)
@click.argument("ip_start", required=True)
@click.argument("ip_end", required=False)
@clicommon.pass_db
def update(db, range_name, ip_start, ip_end):
pass


@range.command()
@click.argument("range_name", required=True)
@clicommon.pass_db
def del(db, range_name)
pass


@ipv4.group(cls=clicommon.AliasedGroup)
def ip():
pass


@ip.command()
@click.argument("vlan_interface", required=True)
@click.argument("interface", required=True)
@click.options("--range", required=False, nargs=-1)
@click.argument("ip_list", required=False, nargs=-1)
@clicommon.pass_db
def bind(db, vlan_interface, interface, range_, ip_list):
dbconn = db.db
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether key exist.
If exist: incremental binding, merge old and new. Need to clarify that cannot bind both ips and ranges in the same time
If not exist: add new entry

key = "DHCP_SERVER_IPV4_PORT|" + vlan_interface + "|" + interface
if range_:
dbconn.hmset("CONFIG_DB", key, {"ranges": ",".join(range_)})
elif ip_list:
dbconn.hmset("CONFIG_DB", key, {"ips": ",".join(ip_list)})
else:
raise Exception("One of range and ip_list should be provided")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to use ctx.fail too



@ip.command()
@click.argument("vlan_interface", required=True)
@click.argument("interface", required=True)
@click.options("--range", required=False, nargs=-1)
@click.argument("ip_list", required=False, nargs=-1)
@clicommon.pass_db
def unbind(db, vlan_interface, interface, range_, ip_list):
pass


@ipv6.group(cls=clicommon.AliasedGroup)
def ip():
pass


@ip.command()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate with line 214?

@click.argument("vlan_interface", required=True)
@click.argument("interface", required=True)
@click.options("--range", required=False, nargs=-1)
@click.argument("ip_list", required=False, nargs=-1)
@clicommon.pass_db
def bind(db, vlan_interface, interface, range_, ip_list):
pass


@ip.command()
@click.argument("vlan_interface", required=True)
@click.argument("interface", required=True)
@click.options("--range", required=False, nargs=-1)
@click.argument("ip_list", required=False, nargs=-1)
@clicommon.pass_db
def unbind(db, vlan_interface, interface, range_, ip_list):
pass


@ipv4.group(cls=clicommon.AliasedGroup)
def option():
pass


@option.command()
@click.argument("option_name", required=True)
@click.argument("option_id", required=True)
@click.argument("type", required=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest changing to no required and default "string" for feature support assigned options which type should not be customized and should follow ietf doc

@click.argument("value", required=True)
@clicommon.pass_db
def add(db, option_name, option_id, type_, value):
assert option_id.isdigit()
assert type_ == "string"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support type ["binary", "boolean", "ipv4-address", "string", "uint8", "uint16", "uint32"]

dbconn = db.db
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently only support unassigned options

key = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|" + option_name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether key exist(expected: not exist)

dbconn.hmset("CONFIG_DB", key, {
"option_id": option_id,
"type": type_,
"value": value,
})


@option.command()
@click.argument("option_name", required=True)
@clicommon.pass_db
def del(db, option_name):
dbconn = db.db
key = "DHCP_SERVER_IPV4_CUSTOMIZED_OPTIONS|" + option_name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether key exist (expected: exist)

dbconn.delete("CONFIG_DB", key)


@option.command()
@click.argument("dhcp_interface", required=True)
@click.argument("option_list", nargs=-1)
@clicommon.pass_db
def bind(db, dhcp_interface, option_list):
dbconn = db.db
key = "DHCP_SERVER_IPV4|" + dhcp_interface
if dbconn.exists("CONFIG_DB", key) and option_list:
Copy link
Contributor

@yaqiangz yaqiangz Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether option_list exist (expected: exist). I think fail if any of option_list not exist is better?

old_value = dbconn.get("CONFIG_DB", key, "customized_options")
if old_value:
option_list = [old_value] + option_list
dbconn.set("CONFIG_DB", key, "customized_options", ",".join(option_list))


@option.command()
@click.argument("dhcp_interface", required=True)
@click.argument("option_list", nargs=-1)
@click.option("--all", required=False, default=False, is_flag=True)
@clicommon.pass_db
def unbind(db, dhcp_interface, option_list, all_):
dbconn = db.db
key = "DHCP_SERVER_IPV4|" + dhcp_interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check whether dhcp_interface/option_list exist

if all_:
dbconn.set("CONFIG_DB", key, "customized_options", "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In yang model, "customized_options" field is not required. I think maybe we can delete this field if it is empty?

else:
old_options = dbconn.get("CONFIG_DB", key, "customized_options")
option_set = set(old_options.split(","))
new_option_set = option_set - set(option_list)
new_options = ",".join(new_option_set)
dbconn.set("CONFIG_DB", key, "customized_options", new_options)


@ipv6.group(cls=clicommon.AliasedGroup)
def option():
pass


@option.command()
@click.argument("option_name", required=True)
@click.argument("option_id", required=True)
@click.argument("type", required=True)
@click.argument("value", required=True)
@clicommon.pass_db
def add(db, option_name, option_id, type_, value):
pass


@option.command()
@click.argument("option_name", required=True)
@clicommon.pass_db
def del(db, option_name):
pass


@option.command()
@click.argument("dhcp_interface", required=True)
@click.argument("option_list", nargs=-1)
@clicommon.pass_db
def bind(db, dhcp_interface, option_list):
pass


@option.command()
@click.argument("dhcp_interface", required=True)
@click.argument("option_list", nargs=-1)
@click.option("--all", required=False, default=False, is_flag=True)
@clicommon.pass_db
def unbind(db, dhcp_interface, option_list, all_):
pass


def register(cli):
# cli.add_command(dhcp_server)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rember to add register

pass
Expand Down
Loading
Loading