Merge "Add 'openstack overcloud node import' command"
This commit is contained in:
commit
105352b8e6
@ -67,6 +67,7 @@ openstack.tripleoclient.v1 =
|
||||
overcloud_image_build = tripleoclient.v1.overcloud_image:BuildOvercloudImage
|
||||
overcloud_image_upload = tripleoclient.v1.overcloud_image:UploadOvercloudImage
|
||||
overcloud_node_delete = tripleoclient.v1.overcloud_node:DeleteNode
|
||||
overcloud_node_import = tripleoclient.v1.overcloud_node:ImportNode
|
||||
overcloud_node_introspect = tripleoclient.v1.overcloud_node:IntrospectNode
|
||||
overcloud_node_provide = tripleoclient.v1.overcloud_node:ProvideNode
|
||||
overcloud_plan_delete = tripleoclient.v1.overcloud_plan:DeletePlan
|
||||
|
@ -13,7 +13,6 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
@ -369,19 +368,15 @@ pxe_ssh,192.168.122.2,stack,"KEY2",00:0b:d0:69:7e:58""")
|
||||
def _check_workflow_call(self, local=True, provide=True,
|
||||
kernel_name='bm-deploy-kernel',
|
||||
ramdisk_name='bm-deploy-ramdisk'):
|
||||
nodes_list = copy.deepcopy(self.nodes_list)
|
||||
for node in nodes_list:
|
||||
if local:
|
||||
node['capabilities'] = {'boot_option': 'local'}
|
||||
else:
|
||||
node['capabilities'] = {'boot_option': 'netboot'}
|
||||
|
||||
call_list = [mock.call(
|
||||
'tripleo.baremetal.v1.register_or_update', workflow_input={
|
||||
'kernel_name': kernel_name,
|
||||
'nodes_json': nodes_list,
|
||||
'nodes_json': self.nodes_list,
|
||||
'queue_name': 'UUID4',
|
||||
'ramdisk_name': ramdisk_name}
|
||||
'ramdisk_name': ramdisk_name,
|
||||
'instance_boot_option': 'local' if local else 'netboot'
|
||||
}
|
||||
)]
|
||||
|
||||
if provide:
|
||||
|
@ -13,11 +13,14 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import copy
|
||||
import json
|
||||
import mock
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from openstackclient.tests import utils as test_utils
|
||||
|
||||
|
||||
from tripleoclient.tests.v1.overcloud_node import fakes
|
||||
from tripleoclient.v1 import overcloud_node
|
||||
|
||||
@ -234,3 +237,133 @@ class TestIntrospectNode(fakes.TestOvercloudNode):
|
||||
self.assertRaises(test_utils.ParserException,
|
||||
self.check_parser,
|
||||
self.cmd, argslist, verifylist)
|
||||
|
||||
|
||||
class TestImportNode(fakes.TestOvercloudNode):
|
||||
|
||||
def setUp(self):
|
||||
super(TestImportNode, self).setUp()
|
||||
|
||||
self.nodes_list = [{
|
||||
"pm_user": "stack",
|
||||
"pm_addr": "192.168.122.1",
|
||||
"pm_password": "KEY1",
|
||||
"pm_type": "pxe_ssh",
|
||||
"mac": [
|
||||
"00:0b:d0:69:7e:59"
|
||||
],
|
||||
}, {
|
||||
"pm_user": "stack",
|
||||
"pm_addr": "192.168.122.2",
|
||||
"pm_password": "KEY2",
|
||||
"pm_type": "pxe_ssh",
|
||||
"mac": [
|
||||
"00:0b:d0:69:7e:58"
|
||||
]
|
||||
}]
|
||||
|
||||
self.json_file = tempfile.NamedTemporaryFile(
|
||||
mode='w', delete=False, suffix='.json')
|
||||
json.dump(self.nodes_list, self.json_file)
|
||||
self.json_file.close()
|
||||
self.addCleanup(os.unlink, self.json_file.name)
|
||||
|
||||
self.workflow = self.app.client_manager.workflow_engine
|
||||
client = self.app.client_manager.tripleoclient
|
||||
self.websocket = client.messaging_websocket()
|
||||
|
||||
# Get the command object to test
|
||||
self.cmd = overcloud_node.ImportNode(self.app, None)
|
||||
|
||||
def _check_workflow_call(self, parsed_args, introspect=False,
|
||||
provide=False, local=True, no_deploy_image=False):
|
||||
self.websocket.wait_for_message.return_value = {
|
||||
"status": "SUCCESS",
|
||||
"message": "Success",
|
||||
"registered_nodes": [{
|
||||
"uuid": "MOCK_NODE_UUID"
|
||||
}]
|
||||
}
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
nodes_list = copy.deepcopy(self.nodes_list)
|
||||
|
||||
call_count = 1
|
||||
call_list = [mock.call(
|
||||
'tripleo.baremetal.v1.register_or_update', workflow_input={
|
||||
'nodes_json': nodes_list,
|
||||
'queue_name': 'UUID4',
|
||||
'kernel_name': None if no_deploy_image else 'bm-deploy-kernel',
|
||||
'ramdisk_name': (None
|
||||
if no_deploy_image else 'bm-deploy-ramdisk'),
|
||||
'instance_boot_option': 'local' if local else 'netboot'
|
||||
}
|
||||
)]
|
||||
|
||||
if introspect:
|
||||
call_count += 1
|
||||
call_list.append(mock.call(
|
||||
'tripleo.baremetal.v1.introspect', workflow_input={
|
||||
'node_uuids': ['MOCK_NODE_UUID'],
|
||||
'queue_name': 'UUID4'}
|
||||
))
|
||||
|
||||
if provide:
|
||||
call_count += 1
|
||||
call_list.append(mock.call(
|
||||
'tripleo.baremetal.v1.provide', workflow_input={
|
||||
'node_uuids': ['MOCK_NODE_UUID'],
|
||||
'queue_name': 'UUID4'
|
||||
}
|
||||
))
|
||||
|
||||
self.workflow.executions.create.assert_has_calls(call_list)
|
||||
self.assertEqual(self.workflow.executions.create.call_count,
|
||||
call_count)
|
||||
|
||||
def test_import_only(self):
|
||||
argslist = [self.json_file.name]
|
||||
verifylist = [('introspect', False),
|
||||
('provide', False)]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
|
||||
self._check_workflow_call(parsed_args)
|
||||
|
||||
def test_import_and_introspect(self):
|
||||
argslist = [self.json_file.name, '--introspect']
|
||||
verifylist = [('introspect', True),
|
||||
('provide', False)]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
|
||||
self._check_workflow_call(parsed_args, introspect=True)
|
||||
|
||||
def test_import_and_provide(self):
|
||||
argslist = [self.json_file.name, '--provide']
|
||||
verifylist = [('introspect', False),
|
||||
('provide', True)]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
|
||||
self._check_workflow_call(parsed_args, provide=True)
|
||||
|
||||
def test_import_and_introspect_and_provide(self):
|
||||
argslist = [self.json_file.name, '--introspect', '--provide']
|
||||
verifylist = [('introspect', True),
|
||||
('provide', True)]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
|
||||
self._check_workflow_call(parsed_args, introspect=True, provide=True)
|
||||
|
||||
def test_import_with_netboot(self):
|
||||
arglist = [self.json_file.name, '--instance-boot-option', 'netboot']
|
||||
verifylist = [('instance_boot_option', 'netboot')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self._check_workflow_call(parsed_args, local=False)
|
||||
|
||||
def test_import_with_no_deployed_image(self):
|
||||
arglist = [self.json_file.name, '--no-deploy-image']
|
||||
verifylist = [('no_deploy_image', True)]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self._check_workflow_call(parsed_args, no_deploy_image=True)
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
from __future__ import print_function
|
||||
import base64
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
@ -26,6 +27,7 @@ import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import time
|
||||
import yaml
|
||||
|
||||
from heatclient.common import event_utils
|
||||
from heatclient.exc import HTTPNotFound
|
||||
@ -837,3 +839,47 @@ def get_roles_info(parsed_args):
|
||||
'swift-storage': (parsed_args.swift_storage_flavor,
|
||||
parsed_args.swift_storage_scale)
|
||||
}
|
||||
|
||||
|
||||
def _csv_to_nodes_dict(nodes_csv):
|
||||
"""Convert CSV to a list of dicts formatted for os_cloud_config
|
||||
|
||||
Given a CSV file in the format below, convert it into the
|
||||
structure expected by os_cloud_config JSON files.
|
||||
|
||||
pm_type, pm_addr, pm_user, pm_password, mac
|
||||
"""
|
||||
|
||||
data = []
|
||||
|
||||
for row in csv.reader(nodes_csv):
|
||||
node = {
|
||||
"pm_user": row[2],
|
||||
"pm_addr": row[1],
|
||||
"pm_password": row[3],
|
||||
"pm_type": row[0],
|
||||
"mac": [
|
||||
row[4]
|
||||
]
|
||||
}
|
||||
data.append(node)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def parse_env_file(env_file, file_type=None):
|
||||
if file_type == 'json' or env_file.name.endswith('.json'):
|
||||
nodes_config = json.load(env_file)
|
||||
elif file_type == 'csv' or env_file.name.endswith('.csv'):
|
||||
nodes_config = _csv_to_nodes_dict(env_file)
|
||||
elif env_file.name.endswith('.yaml'):
|
||||
nodes_config = yaml.safe_load(env_file)
|
||||
else:
|
||||
raise exceptions.InvalidConfiguration(
|
||||
_("Invalid file extension for %s, must be json, yaml or csv") %
|
||||
env_file.name)
|
||||
|
||||
if 'nodes' in nodes_config:
|
||||
nodes_config = nodes_config['nodes']
|
||||
|
||||
return nodes_config
|
||||
|
@ -16,12 +16,10 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import uuid
|
||||
import yaml
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
@ -35,32 +33,6 @@ from tripleoclient import utils
|
||||
from tripleoclient.workflows import baremetal
|
||||
|
||||
|
||||
def _csv_to_nodes_dict(nodes_csv):
|
||||
"""Convert CSV to a list of dicts formatted for os_cloud_config
|
||||
|
||||
Given a CSV file in the format below, convert it into the
|
||||
structure expected by os_cloud_config JSON files.
|
||||
|
||||
pm_type, pm_addr, pm_user, pm_password, mac
|
||||
"""
|
||||
|
||||
data = []
|
||||
|
||||
for row in csv.reader(nodes_csv):
|
||||
node = {
|
||||
"pm_user": row[2],
|
||||
"pm_addr": row[1],
|
||||
"pm_password": row[3],
|
||||
"pm_type": row[0],
|
||||
"mac": [
|
||||
row[4]
|
||||
]
|
||||
}
|
||||
data.append(node)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ValidateInstackEnv(command.Command):
|
||||
"""Validate `instackenv.json` which is used in `baremetal import`."""
|
||||
|
||||
@ -188,19 +160,13 @@ class ImportBaremetal(command.Command):
|
||||
|
||||
self.log.debug("take_action(%s)" % parsed_args)
|
||||
|
||||
if parsed_args.json or parsed_args.file_in.name.endswith('.json'):
|
||||
nodes_config = json.load(parsed_args.file_in)
|
||||
elif parsed_args.csv or parsed_args.file_in.name.endswith('.csv'):
|
||||
nodes_config = _csv_to_nodes_dict(parsed_args.file_in)
|
||||
elif parsed_args.file_in.name.endswith('.yaml'):
|
||||
nodes_config = yaml.safe_load(parsed_args.file_in)
|
||||
else:
|
||||
raise exceptions.InvalidConfiguration(
|
||||
_("Invalid file extension for %s, must be json, yaml or csv") %
|
||||
parsed_args.file_in.name)
|
||||
file_type = None
|
||||
if parsed_args.json:
|
||||
file_type = 'json'
|
||||
elif parsed_args.csv:
|
||||
file_type = 'csv'
|
||||
|
||||
if 'nodes' in nodes_config:
|
||||
nodes_config = nodes_config['nodes']
|
||||
nodes_config = utils.parse_env_file(parsed_args.file_in, file_type)
|
||||
|
||||
client = self.app.client_manager.baremetal
|
||||
if parsed_args.initial_state == "enroll":
|
||||
@ -210,14 +176,6 @@ class ImportBaremetal(command.Command):
|
||||
_("OS_BAREMETAL_API_VERSION must be >=1.11 for use of "
|
||||
"'enroll' provision state; currently %s") % api_version)
|
||||
|
||||
# NOTE (dprince) move this to tripleo-common?
|
||||
for node in nodes_config:
|
||||
caps = node.get('capabilities', {})
|
||||
if not isinstance(caps, dict):
|
||||
caps = utils.capabilities_to_dict(caps)
|
||||
caps.setdefault('boot_option', parsed_args.instance_boot_option)
|
||||
node['capabilities'] = caps
|
||||
|
||||
queue_name = str(uuid.uuid4())
|
||||
|
||||
if parsed_args.no_deploy_image:
|
||||
@ -232,7 +190,8 @@ class ImportBaremetal(command.Command):
|
||||
nodes_json=nodes_config,
|
||||
queue_name=queue_name,
|
||||
kernel_name=deploy_kernel,
|
||||
ramdisk_name=deploy_ramdisk
|
||||
ramdisk_name=deploy_ramdisk,
|
||||
instance_boot_option=parsed_args.instance_boot_option
|
||||
)
|
||||
|
||||
node_uuids = [node['uuid'] for node in nodes]
|
||||
|
@ -13,6 +13,7 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
@ -22,6 +23,7 @@ from openstackclient.i18n import _
|
||||
from tripleo_common import scale
|
||||
|
||||
from tripleoclient import constants
|
||||
from tripleoclient import utils as oooutils
|
||||
from tripleoclient.workflows import baremetal
|
||||
|
||||
|
||||
@ -146,3 +148,66 @@ class IntrospectNode(command.Command):
|
||||
else:
|
||||
baremetal.provide_manageable_nodes(self.app.client_manager,
|
||||
queue_name=queue_name)
|
||||
|
||||
|
||||
class ImportNode(command.Command):
|
||||
"""Import baremetal nodes from a JSON, YAML or CSV file.
|
||||
|
||||
The node status will be set to 'manageable' by default.
|
||||
"""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ImportNode")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ImportNode, self).get_parser(prog_name)
|
||||
parser.add_argument('--introspect',
|
||||
action='store_true',
|
||||
help=_('Introspect the imported nodes'))
|
||||
parser.add_argument('--provide',
|
||||
action='store_true',
|
||||
help=_('Provide (make available) the nodes'))
|
||||
parser.add_argument('--no-deploy-image', action='store_true',
|
||||
help=_('Skip setting the deploy kernel and '
|
||||
'ramdisk.'))
|
||||
parser.add_argument('--instance-boot-option',
|
||||
choices=['local', 'netboot'], default='local',
|
||||
help=_('Whether to set instances for booting from '
|
||||
'local hard drive (local) or network '
|
||||
'(netboot).'))
|
||||
parser.add_argument('env_file', type=argparse.FileType('r'))
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)" % parsed_args)
|
||||
|
||||
nodes_config = oooutils.parse_env_file(parsed_args.env_file)
|
||||
|
||||
queue_name = str(uuid.uuid4())
|
||||
|
||||
if parsed_args.no_deploy_image:
|
||||
deploy_kernel = None
|
||||
deploy_ramdisk = None
|
||||
else:
|
||||
deploy_kernel = 'bm-deploy-kernel'
|
||||
deploy_ramdisk = 'bm-deploy-ramdisk'
|
||||
|
||||
nodes = baremetal.register_or_update(
|
||||
self.app.client_manager,
|
||||
nodes_json=nodes_config,
|
||||
queue_name=queue_name,
|
||||
kernel_name=deploy_kernel,
|
||||
ramdisk_name=deploy_ramdisk,
|
||||
instance_boot_option=parsed_args.instance_boot_option
|
||||
)
|
||||
|
||||
nodes_uuids = [node['uuid'] for node in nodes]
|
||||
|
||||
if parsed_args.introspect:
|
||||
baremetal.introspect(self.app.client_manager,
|
||||
node_uuids=nodes_uuids,
|
||||
queue_name=queue_name)
|
||||
|
||||
if parsed_args.provide:
|
||||
baremetal.provide(self.app.client_manager,
|
||||
node_uuids=nodes_uuids,
|
||||
queue_name=queue_name)
|
||||
|
Loading…
Reference in New Issue
Block a user