Support Extra DHCP Options for IPv4 and IPv6
Add API and DB change for Blueprint extra-dhcp-opts-ipv4-ipv6. Add unit tests for this change. The validation of input extra dhcp options is not included in this commit. A follow-up commit will be added for validation. DocImpact APIImpact Change-Id: I346334568929e50e51dd577cde6a257f4bce8e77 Partially-implements: Blueprint extra-dhcp-opts-ipv4-ipv6
This commit is contained in:
parent
64bd24f9d6
commit
a42928780c
|
@ -31,7 +31,7 @@ from neutron.agent.linux import utils
|
|||
from neutron.common import constants
|
||||
from neutron.common import exceptions
|
||||
from neutron.common import utils as commonutils
|
||||
from neutron.i18n import _LE
|
||||
from neutron.i18n import _LE, _LI
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.openstack.common import uuidutils
|
||||
|
||||
|
@ -549,15 +549,19 @@ class Dnsmasq(DhcpLocalProcess):
|
|||
|
||||
def _output_opts_file(self):
|
||||
"""Write a dnsmasq compatible options file."""
|
||||
options, subnet_index_map = self._generate_opts_per_subnet()
|
||||
options += self._generate_opts_per_port(subnet_index_map)
|
||||
|
||||
name = self.get_conf_file_name('opts')
|
||||
utils.replace_file(name, '\n'.join(options))
|
||||
return name
|
||||
|
||||
def _generate_opts_per_subnet(self):
|
||||
options = []
|
||||
subnet_index_map = {}
|
||||
if self.conf.enable_isolated_metadata:
|
||||
subnet_to_interface_ip = self._make_subnet_interface_ip_map()
|
||||
|
||||
options = []
|
||||
|
||||
isolated_subnets = self.get_isolated_subnets(self.network)
|
||||
dhcp_ips = collections.defaultdict(list)
|
||||
subnet_idx_map = {}
|
||||
for i, subnet in enumerate(self.network.subnets):
|
||||
if (not subnet.enable_dhcp or
|
||||
(subnet.ip_version == 6 and
|
||||
|
@ -574,7 +578,7 @@ class Dnsmasq(DhcpLocalProcess):
|
|||
else:
|
||||
# use the dnsmasq ip as nameservers only if there is no
|
||||
# dns-server submitted by the server
|
||||
subnet_idx_map[subnet.id] = i
|
||||
subnet_index_map[subnet.id] = i
|
||||
|
||||
if self.conf.dhcp_domain and subnet.ip_version == 6:
|
||||
options.append('tag:tag%s,option6:domain-search,%s' %
|
||||
|
@ -619,29 +623,35 @@ class Dnsmasq(DhcpLocalProcess):
|
|||
else:
|
||||
options.append(self._format_option(subnet.ip_version,
|
||||
i, 'router'))
|
||||
return options, subnet_index_map
|
||||
|
||||
def _generate_opts_per_port(self, subnet_index_map):
|
||||
options = []
|
||||
dhcp_ips = collections.defaultdict(list)
|
||||
for port in self.network.ports:
|
||||
if getattr(port, 'extra_dhcp_opts', False):
|
||||
for ip_version in (4, 6):
|
||||
if any(
|
||||
netaddr.IPAddress(ip.ip_address).version == ip_version
|
||||
for ip in port.fixed_ips):
|
||||
options.extend(
|
||||
# TODO(xuhanp):Instead of applying extra_dhcp_opts
|
||||
# to both DHCPv4 and DHCPv6, we need to find a new
|
||||
# way to specify options for v4 and v6
|
||||
# respectively. We also need to validate the option
|
||||
# before applying it.
|
||||
self._format_option(ip_version, port.id,
|
||||
opt.opt_name, opt.opt_value)
|
||||
for opt in port.extra_dhcp_opts)
|
||||
port_ip_versions = set(
|
||||
[netaddr.IPAddress(ip.ip_address).version
|
||||
for ip in port.fixed_ips])
|
||||
for opt in port.extra_dhcp_opts:
|
||||
opt_ip_version = opt.ip_version
|
||||
if opt_ip_version in port_ip_versions:
|
||||
options.append(
|
||||
self._format_option(opt_ip_version, port.id,
|
||||
opt.opt_name, opt.opt_value))
|
||||
else:
|
||||
LOG.info(_LI("Cannot apply dhcp option %(opt)s "
|
||||
"because it's ip_version %(version)d "
|
||||
"is not in port's address IP versions"),
|
||||
{'opt': opt.opt_name,
|
||||
'version': opt_ip_version})
|
||||
|
||||
# provides all dnsmasq ip as dns-server if there is more than
|
||||
# one dnsmasq for a subnet and there is no dns-server submitted
|
||||
# by the server
|
||||
if port.device_owner == constants.DEVICE_OWNER_DHCP:
|
||||
for ip in port.fixed_ips:
|
||||
i = subnet_idx_map.get(ip.subnet_id)
|
||||
i = subnet_index_map.get(ip.subnet_id)
|
||||
if i is None:
|
||||
continue
|
||||
dhcp_ips[i].append(ip.ip_address)
|
||||
|
@ -657,10 +667,7 @@ class Dnsmasq(DhcpLocalProcess):
|
|||
','.join(
|
||||
Dnsmasq._convert_to_literal_addrs(ip_version,
|
||||
vx_ips))))
|
||||
|
||||
name = self.get_conf_file_name('opts')
|
||||
utils.replace_file(name, '\n'.join(options))
|
||||
return name
|
||||
return options
|
||||
|
||||
def _make_subnet_interface_ip_map(self):
|
||||
ip_dev = ip_lib.IPDevice(
|
||||
|
|
|
@ -39,9 +39,12 @@ class ExtraDhcpOpt(model_base.BASEV2, models_v2.HasId):
|
|||
nullable=False)
|
||||
opt_name = sa.Column(sa.String(64), nullable=False)
|
||||
opt_value = sa.Column(sa.String(255), nullable=False)
|
||||
__table_args__ = (sa.UniqueConstraint('port_id',
|
||||
'opt_name',
|
||||
name='uidx_portid_optname'),
|
||||
ip_version = sa.Column(sa.Integer, server_default='4', nullable=False)
|
||||
__table_args__ = (sa.UniqueConstraint(
|
||||
'port_id',
|
||||
'opt_name',
|
||||
'ip_version',
|
||||
name='uniq_extradhcpopts0portid0optname0ipversion'),
|
||||
model_base.BASEV2.__table_args__,)
|
||||
|
||||
# Add a relationship to the Port model in order to instruct SQLAlchemy to
|
||||
|
@ -62,10 +65,12 @@ class ExtraDhcpOptMixin(object):
|
|||
with context.session.begin(subtransactions=True):
|
||||
for dopt in extra_dhcp_opts:
|
||||
if dopt['opt_value']:
|
||||
ip_version = dopt.get('ip_version', 4)
|
||||
db = ExtraDhcpOpt(
|
||||
port_id=port['id'],
|
||||
opt_name=dopt['opt_name'],
|
||||
opt_value=dopt['opt_value'])
|
||||
opt_value=dopt['opt_value'],
|
||||
ip_version=ip_version)
|
||||
context.session.add(db)
|
||||
return self._extend_port_extra_dhcp_opts_dict(context, port)
|
||||
|
||||
|
@ -76,7 +81,8 @@ class ExtraDhcpOptMixin(object):
|
|||
def _get_port_extra_dhcp_opts_binding(self, context, port_id):
|
||||
query = self._model_query(context, ExtraDhcpOpt)
|
||||
binding = query.filter(ExtraDhcpOpt.port_id == port_id)
|
||||
return [{'opt_name': r.opt_name, 'opt_value': r.opt_value}
|
||||
return [{'opt_name': r.opt_name, 'opt_value': r.opt_value,
|
||||
'ip_version': r.ip_version}
|
||||
for r in binding]
|
||||
|
||||
def _update_extra_dhcp_opts_on_port(self, context, id, port,
|
||||
|
@ -93,20 +99,25 @@ class ExtraDhcpOptMixin(object):
|
|||
with context.session.begin(subtransactions=True):
|
||||
for upd_rec in dopts:
|
||||
for opt in opt_db:
|
||||
if opt['opt_name'] == upd_rec['opt_name']:
|
||||
if (opt['opt_name'] == upd_rec['opt_name']
|
||||
and opt['ip_version'] == upd_rec.get(
|
||||
'ip_version', 4)):
|
||||
# to handle deleting of a opt from the port.
|
||||
if upd_rec['opt_value'] is None:
|
||||
context.session.delete(opt)
|
||||
elif opt['opt_value'] != upd_rec['opt_value']:
|
||||
opt.update(
|
||||
{'opt_value': upd_rec['opt_value']})
|
||||
else:
|
||||
if opt['opt_value'] != upd_rec['opt_value']:
|
||||
opt.update(
|
||||
{'opt_value': upd_rec['opt_value']})
|
||||
break
|
||||
else:
|
||||
if upd_rec['opt_value'] is not None:
|
||||
ip_version = upd_rec.get('ip_version', 4)
|
||||
db = ExtraDhcpOpt(
|
||||
port_id=id,
|
||||
opt_name=upd_rec['opt_name'],
|
||||
opt_value=upd_rec['opt_value'])
|
||||
opt_value=upd_rec['opt_value'],
|
||||
ip_version=ip_version)
|
||||
context.session.add(db)
|
||||
|
||||
if updated_port:
|
||||
|
@ -117,7 +128,8 @@ class ExtraDhcpOptMixin(object):
|
|||
|
||||
def _extend_port_dict_extra_dhcp_opt(self, res, port):
|
||||
res[edo_ext.EXTRADHCPOPTS] = [{'opt_name': dho.opt_name,
|
||||
'opt_value': dho.opt_value}
|
||||
'opt_value': dho.opt_value,
|
||||
'ip_version': dho.ip_version}
|
||||
for dho in port.dhcp_opts]
|
||||
return res
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
# Copyright 2015 OpenStack Foundation
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""extra_dhcp_options IPv6 support
|
||||
|
||||
Revision ID: 16cdf118d31d
|
||||
Revises: 14be42f3d0a5
|
||||
Create Date: 2014-10-23 17:04:19.796731
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '16cdf118d31d'
|
||||
down_revision = '14be42f3d0a5'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron.db import migration
|
||||
|
||||
CONSTRAINT_NAME_OLD = 'uidx_portid_optname'
|
||||
CONSTRAINT_NAME_NEW = 'uniq_extradhcpopts0portid0optname0ipversion'
|
||||
TABLE_NAME = 'extradhcpopts'
|
||||
|
||||
|
||||
def upgrade():
|
||||
with migration.remove_fks_from_table(TABLE_NAME):
|
||||
op.drop_constraint(
|
||||
name=CONSTRAINT_NAME_OLD,
|
||||
table_name=TABLE_NAME,
|
||||
type_='unique'
|
||||
)
|
||||
|
||||
op.add_column('extradhcpopts', sa.Column('ip_version', sa.Integer(),
|
||||
server_default='4', nullable=False))
|
||||
op.execute("UPDATE extradhcpopts SET ip_version = 4")
|
||||
|
||||
op.create_unique_constraint(
|
||||
name=CONSTRAINT_NAME_NEW,
|
||||
source='extradhcpopts',
|
||||
local_cols=['port_id', 'opt_name', 'ip_version']
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
with migration.remove_fks_from_table(TABLE_NAME):
|
||||
op.drop_constraint(
|
||||
name=CONSTRAINT_NAME_NEW,
|
||||
table_name='extradhcpopts',
|
||||
type_='unique'
|
||||
)
|
||||
op.drop_column('extradhcpopts', 'ip_version')
|
||||
|
||||
op.create_unique_constraint(
|
||||
name=CONSTRAINT_NAME_OLD,
|
||||
source='extradhcpopts',
|
||||
local_cols=['port_id', 'opt_name']
|
||||
)
|
|
@ -1 +1 @@
|
|||
14be42f3d0a5
|
||||
16cdf118d31d
|
||||
|
|
|
@ -55,7 +55,10 @@ EXTENDED_ATTRIBUTES_2_0 = {
|
|||
'opt_name': {'type:not_empty_string': None,
|
||||
'required': True},
|
||||
'opt_value': {'type:not_empty_string_or_none': None,
|
||||
'required': True}}}}}}
|
||||
'required': True},
|
||||
'ip_version': {'convert_to': attr.convert_to_int,
|
||||
'type:values': [4, 6],
|
||||
'required': False}}}}}}
|
||||
|
||||
|
||||
class Extra_dhcp_opt(extensions.ExtensionDescriptor):
|
||||
|
|
|
@ -64,10 +64,12 @@ class TestExtraDhcpOpt(ExtraDhcpOptDBTestCase):
|
|||
for opt in returned:
|
||||
name = opt['opt_name']
|
||||
for exp in expected:
|
||||
if name == exp['opt_name']:
|
||||
if (name == exp['opt_name']
|
||||
and opt['ip_version'] == exp.get(
|
||||
'ip_version', 4)):
|
||||
val = exp['opt_value']
|
||||
break
|
||||
self.assertEqual(opt['opt_value'], val)
|
||||
self.assertEqual(val, opt['opt_value'])
|
||||
|
||||
def test_create_port_with_extradhcpopts(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name',
|
||||
|
@ -103,6 +105,55 @@ class TestExtraDhcpOpt(ExtraDhcpOptDBTestCase):
|
|||
self._check_opts(expected,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
|
||||
def test_create_port_with_extradhcpopts_ipv4_opt_version(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name',
|
||||
'opt_value': 'pxelinux.0',
|
||||
'ip_version': 4},
|
||||
{'opt_name': 'server-ip-address',
|
||||
'opt_value': '123.123.123.456',
|
||||
'ip_version': 4},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': '123.123.123.123',
|
||||
'ip_version': 4}]
|
||||
|
||||
params = {edo_ext.EXTRADHCPOPTS: opt_list,
|
||||
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
|
||||
|
||||
with self.port(**params) as port:
|
||||
self._check_opts(opt_list,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
|
||||
def test_create_port_with_extradhcpopts_ipv6_opt_version(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name',
|
||||
'opt_value': 'pxelinux.0',
|
||||
'ip_version': 6},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': '2001:192:168::1',
|
||||
'ip_version': 6}]
|
||||
|
||||
params = {edo_ext.EXTRADHCPOPTS: opt_list,
|
||||
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
|
||||
|
||||
with self.port(**params) as port:
|
||||
self._check_opts(opt_list,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
|
||||
def _test_update_port_with_extradhcpopts(self, opt_list, upd_opts,
|
||||
expected_opts):
|
||||
params = {edo_ext.EXTRADHCPOPTS: opt_list,
|
||||
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
|
||||
|
||||
with self.port(**params) as port:
|
||||
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
|
||||
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, webob.exc.HTTPOk.code)
|
||||
port = self.deserialize('json', res)
|
||||
self._check_opts(expected_opts,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
|
||||
def test_update_port_with_extradhcpopts_with_same(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
|
||||
{'opt_name': 'tftp-server',
|
||||
|
@ -115,18 +166,8 @@ class TestExtraDhcpOpt(ExtraDhcpOptDBTestCase):
|
|||
if i['opt_name'] == upd_opts[0]['opt_name']:
|
||||
i['opt_value'] = upd_opts[0]['opt_value']
|
||||
break
|
||||
|
||||
params = {edo_ext.EXTRADHCPOPTS: opt_list,
|
||||
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
|
||||
|
||||
with self.port(**params) as port:
|
||||
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
|
||||
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize('json', req.get_response(self.api))
|
||||
self._check_opts(expected_opts,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
||||
def test_update_port_with_additional_extradhcpopt(self):
|
||||
opt_list = [{'opt_name': 'tftp-server',
|
||||
|
@ -136,17 +177,8 @@ class TestExtraDhcpOpt(ExtraDhcpOptDBTestCase):
|
|||
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': 'changeme.0'}]
|
||||
expected_opts = copy.deepcopy(opt_list)
|
||||
expected_opts.append(upd_opts[0])
|
||||
params = {edo_ext.EXTRADHCPOPTS: opt_list,
|
||||
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
|
||||
|
||||
with self.port(**params) as port:
|
||||
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
|
||||
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize('json', req.get_response(self.api))
|
||||
self._check_opts(expected_opts,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
||||
def test_update_port_with_extradhcpopts(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
|
||||
|
@ -160,18 +192,8 @@ class TestExtraDhcpOpt(ExtraDhcpOptDBTestCase):
|
|||
if i['opt_name'] == upd_opts[0]['opt_name']:
|
||||
i['opt_value'] = upd_opts[0]['opt_value']
|
||||
break
|
||||
|
||||
params = {edo_ext.EXTRADHCPOPTS: opt_list,
|
||||
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
|
||||
|
||||
with self.port(**params) as port:
|
||||
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
|
||||
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize('json', req.get_response(self.api))
|
||||
self._check_opts(expected_opts,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
||||
def test_update_port_with_extradhcpopt_delete(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
|
||||
|
@ -184,45 +206,26 @@ class TestExtraDhcpOpt(ExtraDhcpOptDBTestCase):
|
|||
|
||||
expected_opts = [opt for opt in opt_list
|
||||
if opt['opt_name'] != 'bootfile-name']
|
||||
|
||||
params = {edo_ext.EXTRADHCPOPTS: opt_list,
|
||||
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
|
||||
|
||||
with self.port(**params) as port:
|
||||
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
|
||||
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize('json', req.get_response(self.api))
|
||||
self._check_opts(expected_opts,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
||||
def test_update_port_without_extradhcpopt_delete(self):
|
||||
opt_list = []
|
||||
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': None}]
|
||||
|
||||
with self.port() as port:
|
||||
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
|
||||
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize('json', req.get_response(self.api))
|
||||
edo_attr = port['port'].get(edo_ext.EXTRADHCPOPTS)
|
||||
self.assertEqual(edo_attr, [])
|
||||
expected_opts = []
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
||||
def test_update_port_adding_extradhcpopts(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
|
||||
opt_list = []
|
||||
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': '123.123.123.123'},
|
||||
{'opt_name': 'server-ip-address',
|
||||
'opt_value': '123.123.123.456'}]
|
||||
with self.port() as port:
|
||||
update_port = {'port': {edo_ext.EXTRADHCPOPTS: opt_list}}
|
||||
|
||||
req = self.new_update_request('ports', update_port,
|
||||
port['port']['id'])
|
||||
port = self.deserialize('json', req.get_response(self.api))
|
||||
self._check_opts(opt_list,
|
||||
port['port'][edo_ext.EXTRADHCPOPTS])
|
||||
expected_opts = copy.deepcopy(upd_opts)
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
||||
def test_update_port_with_blank_string_extradhcpopt(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
|
||||
|
@ -261,3 +264,36 @@ class TestExtraDhcpOpt(ExtraDhcpOptDBTestCase):
|
|||
port['port']['id'])
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
|
||||
def test_update_port_with_extradhcpopts_ipv6_change_value(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name',
|
||||
'opt_value': 'pxelinux.0',
|
||||
'ip_version': 6},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': '2001:192:168::1',
|
||||
'ip_version': 6}]
|
||||
upd_opts = [{'opt_name': 'tftp-server',
|
||||
'opt_value': '2001:192:168::2',
|
||||
'ip_version': 6}]
|
||||
expected_opts = copy.deepcopy(opt_list)
|
||||
for i in expected_opts:
|
||||
if i['opt_name'] == upd_opts[0]['opt_name']:
|
||||
i['opt_value'] = upd_opts[0]['opt_value']
|
||||
break
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
||||
def test_update_port_with_extradhcpopts_add_another_ver_opt(self):
|
||||
opt_list = [{'opt_name': 'bootfile-name',
|
||||
'opt_value': 'pxelinux.0',
|
||||
'ip_version': 6},
|
||||
{'opt_name': 'tftp-server',
|
||||
'opt_value': '2001:192:168::1',
|
||||
'ip_version': 6}]
|
||||
upd_opts = [{'opt_name': 'tftp-server',
|
||||
'opt_value': '123.123.123.123',
|
||||
'ip_version': 4}]
|
||||
expected_opts = copy.deepcopy(opt_list)
|
||||
expected_opts.extend(upd_opts)
|
||||
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
|
||||
expected_opts)
|
||||
|
|
|
@ -39,6 +39,7 @@ class FakeIPAllocation(object):
|
|||
|
||||
class DhcpOpt(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(ip_version=4)
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -465,6 +466,34 @@ class FakeV4NetworkPxe3Ports(object):
|
|||
DhcpOpt(opt_name='bootfile-name', opt_value='pxelinux3.0')]
|
||||
|
||||
|
||||
class FakeV6NetworkPxePort(object):
|
||||
id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
|
||||
subnets = [FakeV6SubnetDHCPStateful()]
|
||||
ports = [FakeV6Port()]
|
||||
namespace = 'qdhcp-ns'
|
||||
|
||||
def __init__(self):
|
||||
self.ports[0].extra_dhcp_opts = [
|
||||
DhcpOpt(opt_name='tftp-server', opt_value='2001:192:168::1',
|
||||
ip_version=6),
|
||||
DhcpOpt(opt_name='bootfile-name', opt_value='pxelinux.0',
|
||||
ip_version=6)]
|
||||
|
||||
|
||||
class FakeV6NetworkPxePortWrongOptVersion(object):
|
||||
id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
|
||||
subnets = [FakeV6SubnetDHCPStateful()]
|
||||
ports = [FakeV6Port()]
|
||||
namespace = 'qdhcp-ns'
|
||||
|
||||
def __init__(self):
|
||||
self.ports[0].extra_dhcp_opts = [
|
||||
DhcpOpt(opt_name='tftp-server', opt_value='192.168.0.7',
|
||||
ip_version=4),
|
||||
DhcpOpt(opt_name='bootfile-name', opt_value='pxelinux.0',
|
||||
ip_version=6)]
|
||||
|
||||
|
||||
class FakeDualStackNetworkSingleDHCP(object):
|
||||
id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||
|
||||
|
@ -1036,6 +1065,40 @@ class TestDnsmasq(TestBase):
|
|||
|
||||
self.safe.assert_called_once_with('/foo/opts', expected)
|
||||
|
||||
@mock.patch('neutron.agent.linux.dhcp.Dnsmasq.get_conf_file_name',
|
||||
return_value='/foo/opts')
|
||||
def test_output_opts_file_pxe_ipv6_port_with_ipv6_opt(self,
|
||||
mock_get_conf_fn):
|
||||
expected = (
|
||||
'tag:tag0,option6:dns-server,[2001:0200:feed:7ac0::1]\n'
|
||||
'tag:tag0,option6:domain-search,openstacklocal\n'
|
||||
'tag:hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh,'
|
||||
'option6:tftp-server,2001:192:168::1\n'
|
||||
'tag:hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh,'
|
||||
'option6:bootfile-name,pxelinux.0')
|
||||
expected = expected.lstrip()
|
||||
|
||||
dm = self._get_dnsmasq(FakeV6NetworkPxePort())
|
||||
dm._output_opts_file()
|
||||
|
||||
self.safe.assert_called_once_with('/foo/opts', expected)
|
||||
|
||||
@mock.patch('neutron.agent.linux.dhcp.Dnsmasq.get_conf_file_name',
|
||||
return_value='/foo/opts')
|
||||
def test_output_opts_file_pxe_ipv6_port_with_ipv4_opt(self,
|
||||
mock_get_conf_fn):
|
||||
expected = (
|
||||
'tag:tag0,option6:dns-server,[2001:0200:feed:7ac0::1]\n'
|
||||
'tag:tag0,option6:domain-search,openstacklocal\n'
|
||||
'tag:hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh,'
|
||||
'option6:bootfile-name,pxelinux.0')
|
||||
expected = expected.lstrip()
|
||||
|
||||
dm = self._get_dnsmasq(FakeV6NetworkPxePortWrongOptVersion())
|
||||
dm._output_opts_file()
|
||||
|
||||
self.safe.assert_called_once_with('/foo/opts', expected)
|
||||
|
||||
@property
|
||||
def _test_no_dhcp_domain_alloc_data(self):
|
||||
exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host'
|
||||
|
|
Loading…
Reference in New Issue