Added userdata plugin for setting the hostname
Change-Id: I95fcc5e92806e0de345c02898bd4a4bcfe698399
This commit is contained in:
parent
fccf0e6f00
commit
12a6d8a9ab
cloudbaseinit
plugins/common
tests
utils
@ -42,6 +42,8 @@ TAG_REGEX = {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NO_REBOOT = 0
|
||||||
|
|
||||||
# important return values range
|
# important return values range
|
||||||
RET_START = 1001
|
RET_START = 1001
|
||||||
RET_END = 1003
|
RET_END = 1003
|
||||||
|
@ -12,57 +12,25 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import platform
|
|
||||||
import re
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as oslo_logging
|
from oslo_log import log as oslo_logging
|
||||||
|
|
||||||
from cloudbaseinit.osutils import factory as osutils_factory
|
from cloudbaseinit.osutils import factory as osutils_factory
|
||||||
from cloudbaseinit.plugins.common import base
|
from cloudbaseinit.plugins.common import base
|
||||||
|
from cloudbaseinit.utils import hostname
|
||||||
opts = [
|
|
||||||
cfg.BoolOpt('netbios_host_name_compatibility', default=True,
|
|
||||||
help='Truncates the hostname to 15 characters for Netbios '
|
|
||||||
'compatibility'),
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_opts(opts)
|
|
||||||
|
|
||||||
LOG = oslo_logging.getLogger(__name__)
|
LOG = oslo_logging.getLogger(__name__)
|
||||||
|
|
||||||
NETBIOS_HOST_NAME_MAX_LEN = 15
|
|
||||||
|
|
||||||
|
|
||||||
class SetHostNamePlugin(base.BasePlugin):
|
class SetHostNamePlugin(base.BasePlugin):
|
||||||
def execute(self, service, shared_data):
|
def execute(self, service, shared_data):
|
||||||
osutils = osutils_factory.get_os_utils()
|
osutils = osutils_factory.get_os_utils()
|
||||||
|
|
||||||
metadata_host_name = service.get_host_name()
|
metadata_host_name = service.get_host_name()
|
||||||
|
|
||||||
if not metadata_host_name:
|
if not metadata_host_name:
|
||||||
LOG.debug('Hostname not found in metadata')
|
LOG.debug('Hostname not found in metadata')
|
||||||
return base.PLUGIN_EXECUTION_DONE, False
|
return base.PLUGIN_EXECUTION_DONE, False
|
||||||
|
|
||||||
metadata_host_name = metadata_host_name.split('.', 1)[0]
|
(_, reboot_required) = hostname.set_hostname(
|
||||||
|
osutils, metadata_host_name)
|
||||||
if (len(metadata_host_name) > NETBIOS_HOST_NAME_MAX_LEN and
|
|
||||||
CONF.netbios_host_name_compatibility):
|
|
||||||
new_host_name = metadata_host_name[:NETBIOS_HOST_NAME_MAX_LEN]
|
|
||||||
LOG.warn('Truncating host name for Netbios compatibility. '
|
|
||||||
'Old name: %(metadata_host_name)s, new name: '
|
|
||||||
'%(new_host_name)s' %
|
|
||||||
{'metadata_host_name': metadata_host_name,
|
|
||||||
'new_host_name': new_host_name})
|
|
||||||
else:
|
|
||||||
new_host_name = metadata_host_name
|
|
||||||
|
|
||||||
new_host_name = re.sub(r'-$', '0', new_host_name)
|
|
||||||
if platform.node().lower() == new_host_name.lower():
|
|
||||||
LOG.debug("Hostname already set to: %s" % new_host_name)
|
|
||||||
reboot_required = False
|
|
||||||
else:
|
|
||||||
LOG.info("Setting hostname: %s" % new_host_name)
|
|
||||||
reboot_required = osutils.set_host_name(new_host_name)
|
|
||||||
|
|
||||||
return base.PLUGIN_EXECUTION_DONE, reboot_required
|
return base.PLUGIN_EXECUTION_DONE, reboot_required
|
||||||
|
@ -17,6 +17,7 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log as oslo_logging
|
from oslo_log import log as oslo_logging
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from cloudbaseinit.plugins.common import execcmd
|
||||||
from cloudbaseinit.plugins.common.userdataplugins import base
|
from cloudbaseinit.plugins.common.userdataplugins import base
|
||||||
from cloudbaseinit.plugins.common.userdataplugins.cloudconfigplugins import (
|
from cloudbaseinit.plugins.common.userdataplugins.cloudconfigplugins import (
|
||||||
factory
|
factory
|
||||||
@ -37,7 +38,6 @@ OPTS = [
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_opts(OPTS)
|
CONF.register_opts(OPTS)
|
||||||
DEFAULT_ORDER_VALUE = 999
|
DEFAULT_ORDER_VALUE = 999
|
||||||
REBOOT = 1001
|
|
||||||
|
|
||||||
|
|
||||||
class CloudConfigError(Exception):
|
class CloudConfigError(Exception):
|
||||||
@ -80,7 +80,7 @@ class CloudConfigPluginExecutor(object):
|
|||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
"""Call each plugin, in the order requested by the user."""
|
"""Call each plugin, in the order requested by the user."""
|
||||||
reboot = 0
|
reboot = execcmd.NO_REBOOT
|
||||||
plugins = factory.load_plugins()
|
plugins = factory.load_plugins()
|
||||||
for plugin_name, value in self._expected_plugins:
|
for plugin_name, value in self._expected_plugins:
|
||||||
method = plugins.get(plugin_name)
|
method = plugins.get(plugin_name)
|
||||||
@ -91,7 +91,7 @@ class CloudConfigPluginExecutor(object):
|
|||||||
try:
|
try:
|
||||||
requires_reboot = method(value)
|
requires_reboot = method(value)
|
||||||
if requires_reboot:
|
if requires_reboot:
|
||||||
reboot = REBOOT
|
reboot = execcmd.RET_END
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.exception("Processing plugin %s failed", plugin_name)
|
LOG.exception("Processing plugin %s failed", plugin_name)
|
||||||
return reboot
|
return reboot
|
||||||
|
@ -22,6 +22,8 @@ PLUGINS = {
|
|||||||
'cloudconfigplugins.write_files.WriteFilesPlugin',
|
'cloudconfigplugins.write_files.WriteFilesPlugin',
|
||||||
'set_timezone': 'cloudbaseinit.plugins.common.userdataplugins.'
|
'set_timezone': 'cloudbaseinit.plugins.common.userdataplugins.'
|
||||||
'cloudconfigplugins.set_timezone.SetTimezonePlugin',
|
'cloudconfigplugins.set_timezone.SetTimezonePlugin',
|
||||||
|
'set_hostname': 'cloudbaseinit.plugins.common.userdataplugins.'
|
||||||
|
'cloudconfigplugins.set_hostname.SetHostnamePlugin',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
# Copyright 2015 Cloudbase Solutions Srl
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from oslo_log import log as oslo_logging
|
||||||
|
|
||||||
|
from cloudbaseinit.osutils import factory
|
||||||
|
from cloudbaseinit.plugins.common.userdataplugins.cloudconfigplugins import (
|
||||||
|
base
|
||||||
|
)
|
||||||
|
from cloudbaseinit.utils import hostname
|
||||||
|
|
||||||
|
|
||||||
|
LOG = oslo_logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SetHostnamePlugin(base.BaseCloudConfigPlugin):
|
||||||
|
"""Change the hostname for the underlying platform.
|
||||||
|
|
||||||
|
If the timezone is changed a restart will be required.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def process(self, data):
|
||||||
|
LOG.info("Changing hostname to %r", data)
|
||||||
|
osutils = factory.get_os_utils()
|
||||||
|
_, reboot_required = hostname.set_hostname(osutils, data)
|
||||||
|
return reboot_required
|
@ -21,7 +21,6 @@ except ImportError:
|
|||||||
|
|
||||||
from cloudbaseinit.plugins.common import base
|
from cloudbaseinit.plugins.common import base
|
||||||
from cloudbaseinit.plugins.common import sethostname
|
from cloudbaseinit.plugins.common import sethostname
|
||||||
from cloudbaseinit.tests.metadata import fake_json_response
|
|
||||||
from cloudbaseinit.tests import testutils
|
from cloudbaseinit.tests import testutils
|
||||||
|
|
||||||
|
|
||||||
@ -29,74 +28,37 @@ class SetHostNamePluginPluginTests(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self._sethostname_plugin = sethostname.SetHostNamePlugin()
|
self._sethostname_plugin = sethostname.SetHostNamePlugin()
|
||||||
self.fake_data = fake_json_response.get_fake_metadata_json(
|
|
||||||
'2013-04-04')
|
|
||||||
|
|
||||||
@testutils.ConfPatcher('netbios_host_name_compatibility', True)
|
@testutils.ConfPatcher('netbios_host_name_compatibility', True)
|
||||||
@mock.patch('platform.node')
|
|
||||||
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils')
|
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils')
|
||||||
def _test_execute(self, mock_get_os_utils, mock_node, hostname_exists=True,
|
@mock.patch('cloudbaseinit.utils.hostname.set_hostname')
|
||||||
hostname_already_set=False, new_hostname_length=1,
|
def _test_execute(self, mock_set_hostname, mock_get_os_utils,
|
||||||
hostname_truncate_to_zero=False):
|
hostname_exists=False):
|
||||||
|
new_hostname = 'hostname'
|
||||||
|
shared_data = 'fake_shared_data'
|
||||||
|
mock_get_os_utils.return_value = None
|
||||||
mock_service = mock.MagicMock()
|
mock_service = mock.MagicMock()
|
||||||
mock_osutils = mock.MagicMock()
|
|
||||||
fake_shared_data = 'fake data'
|
|
||||||
new_hostname = 'x' * new_hostname_length
|
|
||||||
|
|
||||||
if hostname_truncate_to_zero:
|
|
||||||
new_hostname = ('%s-') % new_hostname[:-1]
|
|
||||||
|
|
||||||
if hostname_exists:
|
if hostname_exists:
|
||||||
mock_service.get_host_name.return_value = new_hostname
|
mock_service.get_host_name.return_value = new_hostname
|
||||||
else:
|
else:
|
||||||
mock_service.get_host_name.return_value = None
|
mock_service.get_host_name.return_value = None
|
||||||
|
mock_set_hostname.return_value = (new_hostname, True)
|
||||||
|
|
||||||
mock_get_os_utils.return_value = mock_osutils
|
response = self._sethostname_plugin.execute(mock_service, shared_data)
|
||||||
mock_get_os_utils.return_value.set_host_name.return_value = True
|
|
||||||
|
|
||||||
if hostname_exists is True:
|
|
||||||
length = sethostname.NETBIOS_HOST_NAME_MAX_LEN
|
|
||||||
hostname = new_hostname.split('.', 1)[0]
|
|
||||||
if len(new_hostname) > length:
|
|
||||||
hostname = hostname[:length]
|
|
||||||
if hostname_truncate_to_zero:
|
|
||||||
hostname = ('%s0') % hostname[:-1]
|
|
||||||
if hostname_already_set:
|
|
||||||
mock_node.return_value = hostname
|
|
||||||
else:
|
|
||||||
mock_node.return_value = 'fake_old_value'
|
|
||||||
|
|
||||||
response = self._sethostname_plugin.execute(mock_service,
|
|
||||||
fake_shared_data)
|
|
||||||
|
|
||||||
mock_service.get_host_name.assert_called_once_with()
|
mock_service.get_host_name.assert_called_once_with()
|
||||||
|
|
||||||
if hostname_exists is True:
|
if hostname_exists:
|
||||||
mock_get_os_utils.assert_called_once_with()
|
mock_set_hostname.assert_called_once_with(
|
||||||
if hostname_already_set:
|
None, new_hostname)
|
||||||
self.assertFalse(mock_osutils.set_host_name.called)
|
else:
|
||||||
else:
|
self.assertFalse(mock_set_hostname.called)
|
||||||
mock_osutils.set_host_name.assert_called_once_with(hostname)
|
|
||||||
|
|
||||||
self.assertEqual((base.PLUGIN_EXECUTION_DONE,
|
self.assertEqual((base.PLUGIN_EXECUTION_DONE, hostname_exists),
|
||||||
hostname_exists and not hostname_already_set),
|
|
||||||
response)
|
response)
|
||||||
|
|
||||||
def test_execute_hostname_already_set(self):
|
def test_execute_new_hostname(self):
|
||||||
self._test_execute(hostname_already_set=True)
|
self._test_execute(hostname_exists=True)
|
||||||
|
|
||||||
def test_execute_hostname_to_be_truncated(self):
|
|
||||||
self._test_execute(
|
|
||||||
new_hostname_length=sethostname.NETBIOS_HOST_NAME_MAX_LEN + 1)
|
|
||||||
|
|
||||||
def test_execute_no_truncate_needed(self):
|
|
||||||
self._test_execute(
|
|
||||||
new_hostname_length=sethostname.NETBIOS_HOST_NAME_MAX_LEN)
|
|
||||||
|
|
||||||
def test_execute_truncate_to_zero(self):
|
|
||||||
self._test_execute(
|
|
||||||
new_hostname_length=sethostname.NETBIOS_HOST_NAME_MAX_LEN,
|
|
||||||
hostname_truncate_to_zero=True)
|
|
||||||
|
|
||||||
def test_execute_no_hostname(self):
|
def test_execute_no_hostname(self):
|
||||||
self._test_execute(hostname_exists=False)
|
self._test_execute(hostname_exists=False)
|
||||||
|
77
cloudbaseinit/tests/utils/test_hostname.py
Normal file
77
cloudbaseinit/tests/utils/test_hostname.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Copyright 2015 Cloudbase Solutions Srl
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
try:
|
||||||
|
import unittest.mock as mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from cloudbaseinit.tests import testutils
|
||||||
|
from cloudbaseinit.utils import hostname
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class HostnameUtilsTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@testutils.ConfPatcher('netbios_host_name_compatibility', True)
|
||||||
|
@mock.patch('platform.node')
|
||||||
|
def _test_set_hostname(self, mock_node, new_hostname='hostname',
|
||||||
|
expected_new_hostname='hostname',
|
||||||
|
hostname_already_set=False):
|
||||||
|
mock_osutils = mock.MagicMock()
|
||||||
|
mock_osutils.set_host_name.return_value = True
|
||||||
|
|
||||||
|
if hostname_already_set:
|
||||||
|
mock_node.return_value = expected_new_hostname
|
||||||
|
else:
|
||||||
|
mock_node.return_value = 'fake_old_hostname'
|
||||||
|
|
||||||
|
(new_hostname, reboot_required) = hostname.set_hostname(
|
||||||
|
mock_osutils, new_hostname)
|
||||||
|
|
||||||
|
if hostname_already_set:
|
||||||
|
self.assertFalse(mock_osutils.set_host_name.called)
|
||||||
|
else:
|
||||||
|
mock_osutils.set_host_name.assert_called_once_with(
|
||||||
|
expected_new_hostname)
|
||||||
|
|
||||||
|
self.assertEqual((new_hostname, reboot_required),
|
||||||
|
(expected_new_hostname, not hostname_already_set))
|
||||||
|
|
||||||
|
def test_execute_hostname_already_set(self):
|
||||||
|
self._test_set_hostname(hostname_already_set=True)
|
||||||
|
|
||||||
|
def test_execute_hostname_to_be_truncated(self):
|
||||||
|
new_hostname = 'x' * (hostname.NETBIOS_HOST_NAME_MAX_LEN + 1)
|
||||||
|
expected_new_hostname = new_hostname[:-1]
|
||||||
|
self._test_set_hostname(new_hostname=new_hostname,
|
||||||
|
expected_new_hostname=expected_new_hostname)
|
||||||
|
|
||||||
|
def test_execute_no_truncate_needed(self):
|
||||||
|
new_hostname = 'x' * hostname.NETBIOS_HOST_NAME_MAX_LEN
|
||||||
|
expected_new_hostname = 'x' * hostname.NETBIOS_HOST_NAME_MAX_LEN
|
||||||
|
self._test_set_hostname(new_hostname=new_hostname,
|
||||||
|
expected_new_hostname=expected_new_hostname)
|
||||||
|
|
||||||
|
def test_execute_truncate_to_zero(self):
|
||||||
|
new_hostname = 'x' * (hostname.NETBIOS_HOST_NAME_MAX_LEN - 1) + '-'
|
||||||
|
expected_new_hostname = 'x' * (
|
||||||
|
hostname.NETBIOS_HOST_NAME_MAX_LEN - 1) + '0'
|
||||||
|
self._test_set_hostname(new_hostname=new_hostname,
|
||||||
|
expected_new_hostname=expected_new_hostname)
|
69
cloudbaseinit/utils/hostname.py
Normal file
69
cloudbaseinit/utils/hostname.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Copyright 2015 Cloudbase Solutions Srl
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as oslo_logging
|
||||||
|
|
||||||
|
opts = [
|
||||||
|
cfg.BoolOpt('netbios_host_name_compatibility', default=True,
|
||||||
|
help='Truncates the hostname to 15 characters for Netbios '
|
||||||
|
'compatibility'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(opts)
|
||||||
|
|
||||||
|
LOG = oslo_logging.getLogger(__name__)
|
||||||
|
|
||||||
|
NETBIOS_HOST_NAME_MAX_LEN = 15
|
||||||
|
|
||||||
|
|
||||||
|
def set_hostname(osutils, hostname):
|
||||||
|
"""Change the hostname for the underlying platform.
|
||||||
|
|
||||||
|
If netbios_host_name_compatibility is set to True in the configuration
|
||||||
|
file, then the hostname is truncated to NETBIOS_HOST_NAME_MAX_LEN.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
osutils: instance of osutils
|
||||||
|
hostname: the desired hostname
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(new_hostname, reboot_required)
|
||||||
|
new_hostname: the possibly truncated hostname
|
||||||
|
reboot_required: True if the hostname was changed and a reboot is
|
||||||
|
required, False otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
hostname = hostname.split('.', 1)[0]
|
||||||
|
if (len(hostname) > NETBIOS_HOST_NAME_MAX_LEN and
|
||||||
|
CONF.netbios_host_name_compatibility):
|
||||||
|
LOG.warn('Truncating host name for Netbios compatibility. '
|
||||||
|
'Old name: %(old_hostname)s, new name: '
|
||||||
|
'%(new_hostname)s' %
|
||||||
|
{'old_hostname': hostname,
|
||||||
|
'new_hostname': hostname[:NETBIOS_HOST_NAME_MAX_LEN]})
|
||||||
|
hostname = hostname[:NETBIOS_HOST_NAME_MAX_LEN]
|
||||||
|
hostname = re.sub(r'-$', '0', hostname)
|
||||||
|
if platform.node().lower() == hostname.lower():
|
||||||
|
LOG.debug("Hostname already set to: %s" % hostname)
|
||||||
|
reboot_required = False
|
||||||
|
else:
|
||||||
|
LOG.info("Setting hostname: %s" % hostname)
|
||||||
|
reboot_required = osutils.set_host_name(hostname)
|
||||||
|
|
||||||
|
return hostname, reboot_required
|
Loading…
x
Reference in New Issue
Block a user