Introduce command: xenapi_bootstrap for kolla deployment.
This commit introduces a command which can be invoked by kolla playbook to bootstrap XenAPI - xenapi_bootstrap. This command will invoke some modules's functions to do needed boostrap tasks. At the moment it includes: * configure himn * configure iptalbes to allow traffic * install xapi plugins to dom0 * gather XenAPI facts and save them into a file The facts file will used others e.g. kolla deployment can get facts from it and populate relative configures basing on the facts. Change-Id: Ie2d7d40f2755580aac4a10f3d302190a8bd4fe6f
This commit is contained in:
parent
ebefaa7d0a
commit
f88521682e
106
os_xenapi/cmd/bootstrap.py
Normal file
106
os_xenapi/cmd/bootstrap.py
Normal file
@ -0,0 +1,106 @@
|
||||
# Copyright 2017 Citrix Systems
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Command for XenAPI bootstrap.
|
||||
|
||||
It contains any needed work to bootstrap a XenServer node, so that it's in
|
||||
a good state to proceed for further OpenStack deployment."""
|
||||
|
||||
import getopt
|
||||
import json
|
||||
import sys
|
||||
|
||||
from os_xenapi.utils.himn import config_himn
|
||||
from os_xenapi.utils.iptables import config_iptables
|
||||
from os_xenapi.utils.sshclient import SSHClient
|
||||
from os_xenapi.utils.xapi_plugin import install_plugins_to_dom0
|
||||
from os_xenapi.utils.xenapi_facts import get_xenapi_facts
|
||||
|
||||
USAGE_MSG = "Run the following command to bootstrap the XenAPI compute node:\n"
|
||||
USAGE_MSG += sys.argv[0]
|
||||
USAGE_MSG += " [-i|--himn-ip] <XenServer's HIMN IP>"
|
||||
USAGE_MSG += " [-f|--xenapi-facts-file] <file path to save xenapi facts in>"
|
||||
USAGE_MSG += " [-u|--user-name] <user-name>"
|
||||
USAGE_MSG += " [-p|--passwd] <passwd>\n\n"
|
||||
|
||||
DEF_XENAPI_FACTS_FILE = '/etc/xenapi_facts.json'
|
||||
|
||||
|
||||
def exit_with_usage():
|
||||
sys.stderr.write(USAGE_MSG)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_and_store_facts(dom0_client, file_path):
|
||||
facts = get_xenapi_facts(dom0_client)
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(json.dumps(facts, indent=4, sort_keys=True))
|
||||
|
||||
|
||||
def _parse_args(argv):
|
||||
VALID_OPS_SHORT_STR = "i:f:p:u:"
|
||||
VALID_OPS_LONG_LST = ["himn-ip", "xenapi-facts-file",
|
||||
"passwd", "user-name"]
|
||||
MANDATORY_OPT_LST = ["himn-ip", "passwd", "user-name"]
|
||||
opt_values = {}
|
||||
|
||||
if len(argv) < 2:
|
||||
return exit_with_usage()
|
||||
argv = argv[1:]
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, VALID_OPS_SHORT_STR,
|
||||
VALID_OPS_LONG_LST)
|
||||
except getopt.GetoptError:
|
||||
return exit_with_usage()
|
||||
|
||||
# Get the values from input parameters.
|
||||
for opt, arg in opts:
|
||||
if opt in ("-i", "--himn-ip"):
|
||||
opt_values['himn-ip'] = arg
|
||||
elif opt in ("-f", "--xenapi-facts-file"):
|
||||
opt_values['xenapi-facts-file'] = arg
|
||||
elif opt in ("-p", "--passwd"):
|
||||
opt_values['passwd'] = arg
|
||||
elif opt in ("-u", "--user-name"):
|
||||
opt_values['user-name'] = arg
|
||||
|
||||
# Ensure mandatory opts are all provided.
|
||||
for opt in MANDATORY_OPT_LST:
|
||||
if opt not in opt_values:
|
||||
return exit_with_usage()
|
||||
|
||||
return opt_values
|
||||
|
||||
|
||||
def main():
|
||||
opt_values = _parse_args(sys.argv)
|
||||
|
||||
himn_ip = opt_values['himn-ip']
|
||||
user_name = opt_values['user-name']
|
||||
passwd = opt_values['passwd']
|
||||
# Use DEF_XENAPI_FACTS_FILE if none provided via commandline.
|
||||
facts_file = opt_values.get('xenapi-facts-file', DEF_XENAPI_FACTS_FILE)
|
||||
dom0_client = SSHClient(himn_ip, user_name, passwd)
|
||||
|
||||
# Invoke functions to do needed boostrap tasks.
|
||||
config_himn(himn_ip)
|
||||
config_iptables(dom0_client)
|
||||
install_plugins_to_dom0(dom0_client)
|
||||
|
||||
# Gather XenAPI relative facts and save them into file.
|
||||
get_and_store_facts(dom0_client, facts_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
93
os_xenapi/tests/cmd/test_bootstrap.py
Normal file
93
os_xenapi/tests/cmd/test_bootstrap.py
Normal file
@ -0,0 +1,93 @@
|
||||
# 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 mock
|
||||
|
||||
from os_xenapi.cmd import bootstrap
|
||||
from os_xenapi.tests import base
|
||||
|
||||
|
||||
class GetXenapiFactsTestCase(base.TestCase):
|
||||
def test_parse_args(self):
|
||||
argv = ['bootstrap', '-i', '169.254.0.1', '-u', 'root', '-p', 'passwd']
|
||||
|
||||
return_opts = bootstrap._parse_args(argv)
|
||||
|
||||
expect_opts = {'himn-ip': '169.254.0.1',
|
||||
'passwd': 'passwd',
|
||||
'user-name': 'root'}
|
||||
self.assertEqual(expect_opts, return_opts)
|
||||
|
||||
def test_parse_args_with_filepath(self):
|
||||
argv = ['bootstrap', '-i', '169.254.0.1', '-u', 'root', '-p', 'passwd',
|
||||
'-f', '/path/to/file']
|
||||
|
||||
return_opts = bootstrap._parse_args(argv)
|
||||
|
||||
expect_opts = {'himn-ip': '169.254.0.1',
|
||||
'passwd': 'passwd',
|
||||
'user-name': 'root',
|
||||
'xenapi-facts-file': '/path/to/file'}
|
||||
self.assertEqual(expect_opts, return_opts)
|
||||
|
||||
@mock.patch.object(bootstrap, 'exit_with_usage')
|
||||
def test_parse_args_no_valid_option(self, mock_usage):
|
||||
# Verify if it will exit with prompting usage if no
|
||||
# valid options passed except the command name.
|
||||
argv = ['bootstrap']
|
||||
|
||||
bootstrap._parse_args(argv)
|
||||
|
||||
mock_usage.assert_called_with()
|
||||
|
||||
@mock.patch.object(bootstrap, 'exit_with_usage')
|
||||
def test_parse_args_invalid_opts(self, mock_usage):
|
||||
# Verify if it will exit with prompting usage if pass in
|
||||
# wrong opts.
|
||||
argv = ['bootstrap', '-v', 'invalid_opt']
|
||||
|
||||
bootstrap._parse_args(argv)
|
||||
|
||||
mock_usage.assert_called_with()
|
||||
|
||||
@mock.patch.object(bootstrap, 'exit_with_usage')
|
||||
def test_parse_args_lack_opts(self, mock_usage):
|
||||
# Verify if it will exit with prompting usage if not
|
||||
# pass in all required opts.
|
||||
argv = ['bootstrap', '-i', '169.254.0.1']
|
||||
|
||||
bootstrap._parse_args(argv)
|
||||
|
||||
mock_usage.assert_called_with()
|
||||
|
||||
@mock.patch.object(bootstrap, '_parse_args')
|
||||
@mock.patch.object(bootstrap, 'SSHClient')
|
||||
@mock.patch.object(bootstrap, 'config_himn')
|
||||
@mock.patch.object(bootstrap, 'config_iptables')
|
||||
@mock.patch.object(bootstrap, 'install_plugins_to_dom0')
|
||||
@mock.patch.object(bootstrap, 'get_and_store_facts')
|
||||
def test_bootstrap(self, mock_facts, mock_plugin, mock_iptables,
|
||||
mock_himn, mock_client, mock_parse):
|
||||
fake_opts = {'himn-ip': '169.254.0.1',
|
||||
'passwd': 'passwd',
|
||||
'user-name': 'root'}
|
||||
mock_parse.return_value = fake_opts
|
||||
mock_client.return_value = mock.sentinel.sshclient
|
||||
|
||||
bootstrap.main()
|
||||
|
||||
mock_client.assert_called_with('169.254.0.1', 'root', 'passwd')
|
||||
mock_himn.assert_called_with('169.254.0.1')
|
||||
mock_iptables.assert_called_with(mock.sentinel.sshclient)
|
||||
mock_plugin.assert_called_with(mock.sentinel.sshclient)
|
||||
mock_facts.assert_called_with(mock.sentinel.sshclient,
|
||||
bootstrap.DEF_XENAPI_FACTS_FILE)
|
Loading…
Reference in New Issue
Block a user