Merge "Added VIM host-audit to sysinv API/CLI"
This commit is contained in:
@@ -13683,3 +13683,55 @@ Will reply with updated kernel value
|
||||
"kernel_provisioned": "lowlatency",
|
||||
"kernel_running": "standard"
|
||||
}
|
||||
|
||||
|
||||
-------------------------
|
||||
Host VIM Actions
|
||||
-------------------------
|
||||
|
||||
These APIs allow the user to trigger actions in VIM.
|
||||
|
||||
Supported actions:
|
||||
- host-audit
|
||||
|
||||
********************
|
||||
Trigger Action
|
||||
********************
|
||||
|
||||
.. rest_method:: POST /v1/ihosts/{ihost_uuid}/vim
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
200
|
||||
|
||||
**Error response codes**
|
||||
|
||||
computeFault (400, 500, ...), serviceUnavailable (503),
|
||||
unauthorized (401), forbidden (403), itemNotFound (404)
|
||||
|
||||
**Request parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"ihost_uuid", "URI", "csapi:UUID", "The unique identifier of the host"
|
||||
"vim_event", "plain", "xsd:string", "The action to trigger (host-audit)"
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. csv-table::
|
||||
:header: "Parameter", "Style", "Type", "Description"
|
||||
:widths: 20, 20, 20, 60
|
||||
|
||||
"ihost_uuid", "plain", "csapi:UUID", "The unique identifier of the host"
|
||||
"hostname", "plain", "xsd:string", "The host name"
|
||||
"vim_event", "plain", "xsd:string", "The action that was triggered"
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"ihost_uuid": "e551b1f0-ab6d-43a9-8eb1-05c39025a161",
|
||||
"hostname": "controller-0",
|
||||
"vim_event": "host-audit",
|
||||
}
|
@@ -15,7 +15,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2013-2023 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2023,2025 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ KERNEL = {'ihost_uuid': IHOST['uuid'],
|
||||
UPDATED_KERNEL = copy.deepcopy(KERNEL)
|
||||
NEW_KERNEL = 'lowlatency'
|
||||
UPDATED_KERNEL['kernel_provisioned'] = NEW_KERNEL
|
||||
VIM_HOST_AUDIT_RESPONSE = {"vim_event": "host-audit"}
|
||||
|
||||
fixtures = {
|
||||
'/v1/ihosts':
|
||||
@@ -107,6 +108,13 @@ fixtures = {
|
||||
UPDATED_KERNEL,
|
||||
),
|
||||
},
|
||||
'/v1/ihosts/%s/vim' % IHOST['uuid']:
|
||||
{
|
||||
'POST': (
|
||||
{},
|
||||
VIM_HOST_AUDIT_RESPONSE,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -182,3 +190,11 @@ class HostManagerTest(testtools.TestCase):
|
||||
self.assertEqual(self.api.calls, expect)
|
||||
self.assertEqual(kernel.kernel_provisioned, 'standard')
|
||||
self.assertEqual(kernel.kernel_running, 'standard')
|
||||
|
||||
def test_vim_host_audit(self):
|
||||
self.mgr.vim_host_audit(hostid=IHOST['uuid'])
|
||||
response = {"vim_event": "host-audit"}
|
||||
expect = [
|
||||
('POST', f'/v1/ihosts/{IHOST["uuid"]}/vim', {}, response),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
|
@@ -250,3 +250,11 @@ class HostTest(test_shell.ShellTest):
|
||||
FAKE_KERNEL['kernel_provisioned'])
|
||||
self.assertEqual(kernel['kernel_running'],
|
||||
FAKE_KERNEL['kernel_running'])
|
||||
|
||||
@mock.patch('cgtsclient.v1.ihost.ihostManager.vim_host_audit')
|
||||
def test_vim_host_audit(self, mock_vim_host_audit):
|
||||
self.make_env()
|
||||
mock_vim_host_audit.return_value = None
|
||||
results = self.shell(f"vim-host-audit {FAKE_IHOST['hostname']}")
|
||||
self.assertIn("Host audit initiated successfully", results)
|
||||
mock_vim_host_audit.assert_called_once_with(FAKE_IHOST['uuid'])
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2013-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@@ -686,3 +686,17 @@ def do_host_kernel_show(cc, args):
|
||||
except exc.HTTPNotFound:
|
||||
raise exc.CommandError('Host not found: %s' % args.hostnameorid)
|
||||
_print_kernel_show(kernel, args.format)
|
||||
|
||||
|
||||
@utils.arg('hostnameorid', metavar='<hostname or id>',
|
||||
help="Name or ID of host")
|
||||
def do_vim_host_audit(cc, args):
|
||||
"""Perform host audit operation on specified host."""
|
||||
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
|
||||
try:
|
||||
cc.ihost.vim_host_audit(ihost.uuid)
|
||||
print(f"Host audit initiated successfully: {ihost.hostname}")
|
||||
except exc.HTTPNotFound:
|
||||
print("Host audit failed: host not found")
|
||||
except Exception as e:
|
||||
print(f"Host audit failed: {e}")
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2013-2023 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2023,2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@@ -11,6 +11,7 @@ from cgtsclient.common import base
|
||||
from cgtsclient.common import utils
|
||||
from cgtsclient import exc
|
||||
from cgtsclient.v1 import icpu
|
||||
from sysinv.common import constants
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['hostname', 'personality', 'subfunctions', 'mgmt_mac',
|
||||
@@ -32,6 +33,11 @@ class ihost_kernel(base.Resource):
|
||||
return "<kernel %s>" % self._info
|
||||
|
||||
|
||||
class ihost_vim(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<vim %s>" % self._info
|
||||
|
||||
|
||||
class ihostManager(base.Manager):
|
||||
resource_class = ihost
|
||||
|
||||
@@ -152,6 +158,13 @@ class ihostManager(base.Manager):
|
||||
resp, body = self.api.json_request('GET', url)
|
||||
return ihost_kernel(self, body)
|
||||
|
||||
def vim_host_audit(self, hostid):
|
||||
# path = self._path(hostid) + "/vim"
|
||||
url = self._path(hostid) + "/vim"
|
||||
body = {"vim_event": constants.HOST_AUDIT_ACTION}
|
||||
resp, body = self.api.json_request('POST', url, body=body)
|
||||
return ihost_vim(self, body)
|
||||
|
||||
|
||||
def _find_ihost(cc, ihost_id):
|
||||
if ihost_id.isdigit() or utils.is_uuid_like(ihost_id):
|
||||
|
@@ -22,6 +22,7 @@ install_command = pip install \
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-e{[tox]stxdir}/config/tsconfig/tsconfig
|
||||
-e{[tox]stxdir}/config/sysinv/sysinv/sysinv
|
||||
|
||||
commands =
|
||||
find {toxinidir} -not -path '{toxinidir}/.tox/*' -name '*.py[c|o]' -delete
|
||||
|
@@ -312,6 +312,9 @@ class V1(base.APIBase):
|
||||
evaluate_apps_reapply = [link.Link]
|
||||
"Links to the evaluate_apps_reapply resource"
|
||||
|
||||
vim = [link.Link]
|
||||
"Links to the VIM resource"
|
||||
|
||||
@classmethod
|
||||
def convert(self):
|
||||
v1 = V1()
|
||||
@@ -949,6 +952,13 @@ class V1(base.APIBase):
|
||||
'evaluate_apps_reapply', '',
|
||||
bookmark=True)]
|
||||
|
||||
v1.vim = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'vim', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'vim', '',
|
||||
bookmark=True)]
|
||||
|
||||
return v1
|
||||
|
||||
|
||||
|
@@ -88,6 +88,7 @@ from sysinv.api.controllers.v1 import patch_api
|
||||
from sysinv.api.controllers.v1 import ptp_instance
|
||||
from sysinv.api.controllers.v1 import ptp_interface
|
||||
from sysinv.api.controllers.v1 import kernel
|
||||
from sysinv.api.controllers.v1 import vim
|
||||
from sysinv.api.policies import ihosts as ihosts_policy
|
||||
from sysinv.common import ceph
|
||||
from sysinv.common import constants
|
||||
@@ -1175,6 +1176,9 @@ class HostController(rest.RestController):
|
||||
kernel = kernel.KernelController()
|
||||
"Expose kernel as a sub-element of ihosts"
|
||||
|
||||
vim = vim.VIMController()
|
||||
"Expose vim as a sub-element of ihosts"
|
||||
|
||||
_custom_actions = {
|
||||
'detail': ['GET'],
|
||||
'bulk_add': ['POST'],
|
||||
|
83
sysinv/sysinv/sysinv/sysinv/api/controllers/v1/vim.py
Normal file
83
sysinv/sysinv/sysinv/sysinv/api/controllers/v1/vim.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from oslo_log import log
|
||||
from sysinv.api.controllers.v1 import base
|
||||
from sysinv.api.controllers.v1 import types
|
||||
from sysinv.common import constants
|
||||
from sysinv.common import exception
|
||||
from sysinv.api.controllers.v1 import vim_api
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class VIMHostAudit(base.APIBase):
|
||||
"""API representation of a host audit operation."""
|
||||
|
||||
vim_event = wtypes.text
|
||||
"The VIM event"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = ['vim_event']
|
||||
for k in self.fields:
|
||||
setattr(self, k, kwargs.get(k))
|
||||
|
||||
|
||||
class VIMHostAuditResponse(base.APIBase):
|
||||
"""API representation of a host audit operation."""
|
||||
|
||||
hostname = wtypes.text
|
||||
"The hostname of the host being audited"
|
||||
|
||||
ihost_uuid = types.uuid
|
||||
"The UUID of the host being audited"
|
||||
|
||||
vim_event = wtypes.text
|
||||
"The VIM event"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = ['hostname', 'ihost_uuid', 'vim_event']
|
||||
for k in self.fields:
|
||||
setattr(self, k, kwargs.get(k))
|
||||
|
||||
|
||||
class VIMController(rest.RestController):
|
||||
"""REST controller for VIM operations."""
|
||||
|
||||
def __init__(self):
|
||||
self._api_token = None
|
||||
|
||||
# POST ihosts/<uuid>/vim
|
||||
@wsme_pecan.wsexpose(VIMHostAuditResponse, types.uuid, body=VIMHostAudit)
|
||||
def post(self, host_uuid, event_request):
|
||||
"""Perform host audit operation on specified hosts."""
|
||||
|
||||
host = pecan.request.dbapi.ihost_get(host_uuid)
|
||||
|
||||
if event_request.vim_event != constants.HOST_AUDIT_ACTION:
|
||||
raise exception.InvalidVIMAction(vim_event=event_request.vim_event)
|
||||
|
||||
try:
|
||||
vim_api.vim_host_action(
|
||||
token=self._api_token,
|
||||
uuid=host_uuid,
|
||||
hostname=host.hostname,
|
||||
action=constants.HOST_AUDIT_ACTION,
|
||||
timeout=constants.VIM_DEFAULT_TIMEOUT_IN_SECS,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise exception.CannotTriggerVIMHostAudit(hostname=host.hostname) from e
|
||||
|
||||
return VIMHostAuditResponse(
|
||||
hostname=host.hostname,
|
||||
ihost_uuid=host.uuid,
|
||||
vim_event=constants.HOST_AUDIT_ACTION,
|
||||
)
|
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2015-2020 Wind River Systems, Inc.
|
||||
# Copyright (c) 2015-2020,2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@@ -102,7 +102,8 @@ def vim_host_action(token, uuid, hostname, action, timeout):
|
||||
_valid_actions = [constants.UNLOCK_ACTION,
|
||||
constants.LOCK_ACTION,
|
||||
constants.FORCE_LOCK_ACTION,
|
||||
constants.FORCE_UNSAFE_LOCK_ACTION]
|
||||
constants.FORCE_UNSAFE_LOCK_ACTION,
|
||||
constants.HOST_AUDIT_ACTION]
|
||||
|
||||
if action not in _valid_actions:
|
||||
LOG.error("Unrecognized vim_host_action=%s" % action)
|
||||
|
@@ -86,6 +86,7 @@ FORCE_UNLOCK_ACTION = 'force-unlock'
|
||||
LOCK_ACTION = 'lock'
|
||||
FORCE_LOCK_ACTION = 'force-lock'
|
||||
FORCE_UNSAFE_LOCK_ACTION = 'force-unsafe-lock'
|
||||
HOST_AUDIT_ACTION = 'host-audit'
|
||||
REBOOT_ACTION = 'reboot'
|
||||
RESET_ACTION = 'reset'
|
||||
REINSTALL_ACTION = 'reinstall'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2013-2024 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2025 Wind River Systems, Inc.
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
@@ -275,6 +275,11 @@ class ManagedIPAddress(Invalid):
|
||||
"modified.")
|
||||
|
||||
|
||||
class InvalidVIMAction(Invalid):
|
||||
message = _("Unsupported action: %(vim_event)s, "
|
||||
"only host-audit action is supported")
|
||||
|
||||
|
||||
class AddressAlreadyExists(Conflict):
|
||||
message = _("Address %(address)s/%(prefix)s already "
|
||||
"exists on this interface.")
|
||||
@@ -1725,3 +1730,7 @@ class UnexpectedEvent(SysinvException):
|
||||
|
||||
class CannotQueryPlatformUpgrade(SysinvException):
|
||||
message = _("Failed to query platform upgrade state")
|
||||
|
||||
|
||||
class CannotTriggerVIMHostAudit(SysinvException):
|
||||
message = _("Failed to trigger VIM host-audit for %(hostname)s")
|
||||
|
68
sysinv/sysinv/sysinv/sysinv/tests/api/test_vim.py
Normal file
68
sysinv/sysinv/sysinv/sysinv/tests/api/test_vim.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
"""
|
||||
Tests for the API /ihosts/<uuid>/vim methods.
|
||||
"""
|
||||
import mock
|
||||
from six.moves import http_client
|
||||
from sysinv.common import constants
|
||||
from sysinv.tests.api import base
|
||||
from sysinv.tests.db import base as dbbase
|
||||
|
||||
|
||||
class TestVIM(base.FunctionalTest, dbbase.BaseHostTestCase):
|
||||
# API_HEADERS are a generic header passed to most API calls
|
||||
API_HEADERS = {'User-Agent': 'sysinv-test'}
|
||||
|
||||
def _get_path(self, host_uuid):
|
||||
return f'/ihosts/{host_uuid}/vim'
|
||||
|
||||
def _create_host(self, personality, subfunction=None,
|
||||
mgmt_mac=None, mgmt_ip=None,
|
||||
admin=None,
|
||||
invprovision=constants.PROVISIONED, **kw):
|
||||
host = self._create_test_host(personality=personality,
|
||||
subfunction=subfunction,
|
||||
administrative=(admin or
|
||||
constants.ADMIN_UNLOCKED),
|
||||
invprovision=invprovision,
|
||||
**kw)
|
||||
return host
|
||||
|
||||
|
||||
class VIMHostAuditTestCase(TestVIM):
|
||||
@mock.patch('sysinv.api.controllers.v1.vim_api.vim_host_action')
|
||||
def test_vim_host_audit(self, mock_vim_host_action):
|
||||
worker = self._create_host(constants.WORKER,
|
||||
admin=constants.ADMIN_LOCKED)
|
||||
host_uuid = worker['uuid']
|
||||
data = {"vim_event": "host-audit"}
|
||||
response = self.post_json(self._get_path(host_uuid), data, headers=self.API_HEADERS)
|
||||
self.assertEqual(http_client.OK, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(response.json['vim_event'], constants.HOST_AUDIT_ACTION)
|
||||
self.assertEqual(response.json['ihost_uuid'], host_uuid)
|
||||
self.assertEqual(response.json['hostname'], worker['hostname'])
|
||||
mock_vim_host_action.assert_called_once_with(
|
||||
token=mock.ANY,
|
||||
uuid=worker["uuid"],
|
||||
hostname=worker["hostname"],
|
||||
action=constants.HOST_AUDIT_ACTION,
|
||||
timeout=constants.VIM_DEFAULT_TIMEOUT_IN_SECS
|
||||
)
|
||||
|
||||
@mock.patch('sysinv.api.controllers.v1.vim_api.vim_host_action')
|
||||
def test_vim_host_audit_invalid_action(self, mock_vim_host_action):
|
||||
worker = self._create_host(constants.WORKER,
|
||||
admin=constants.ADMIN_LOCKED)
|
||||
host_uuid = worker['uuid']
|
||||
data = {"vim_event": "invalid-action"}
|
||||
response = self.post_json(self._get_path(host_uuid), data, headers=self.API_HEADERS,
|
||||
expect_errors=True)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertIn("Unsupported action", response.json['error_message'])
|
||||
mock_vim_host_action.assert_not_called()
|
92
sysinv/sysinv/sysinv/sysinv/tests/api/test_vim_api.py
Normal file
92
sysinv/sysinv/sysinv/sysinv/tests/api/test_vim_api.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import mock
|
||||
import json
|
||||
|
||||
from sysinv.common import constants
|
||||
from sysinv.tests.api import base
|
||||
from sysinv.api.controllers.v1 import vim_api
|
||||
|
||||
|
||||
class VimApiTestCase(base.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(VimApiTestCase, self).setUp()
|
||||
|
||||
@mock.patch('sysinv.api.controllers.v1.vim_api.rest_api_request')
|
||||
def test_vim_host_action_audit(self, mock_rest_api_request):
|
||||
# Mock the rest_api_request response
|
||||
mock_rest_api_request.return_value = {'status': 'success'}
|
||||
|
||||
# Test parameters
|
||||
token = None
|
||||
uuid = '1be26c0b-03f2-4d2e-ae87-c02d7f33c123'
|
||||
hostname = 'controller-0'
|
||||
action = constants.HOST_AUDIT_ACTION
|
||||
timeout = constants.VIM_DEFAULT_TIMEOUT_IN_SECS
|
||||
|
||||
# Call the function
|
||||
result = vim_api.vim_host_action(token, uuid, hostname, action, timeout)
|
||||
|
||||
# Verify the result
|
||||
self.assertEqual(result, {'status': 'success'})
|
||||
|
||||
# Verify rest_api_request was called with the correct parameters
|
||||
expected_url = "http://localhost:30001/nfvi-plugins/v1/hosts/%s" % uuid
|
||||
expected_headers = {
|
||||
'Content-type': 'application/json',
|
||||
'User-Agent': 'sysinv/1.0'
|
||||
}
|
||||
expected_payload = {
|
||||
'uuid': uuid,
|
||||
'hostname': hostname,
|
||||
'action': action
|
||||
}
|
||||
|
||||
mock_rest_api_request.assert_called_once_with(
|
||||
token,
|
||||
"PATCH",
|
||||
expected_url,
|
||||
expected_headers,
|
||||
json.dumps(expected_payload),
|
||||
timeout
|
||||
)
|
||||
|
||||
def test_vim_host_action_invalid_action(self):
|
||||
# Test with an invalid action
|
||||
token = None
|
||||
uuid = '1be26c0b-03f2-4d2e-ae87-c02d7f33c123'
|
||||
hostname = 'controller-0'
|
||||
action = 'invalid-action'
|
||||
timeout = constants.VIM_DEFAULT_TIMEOUT_IN_SECS
|
||||
|
||||
# Call the function
|
||||
result = vim_api.vim_host_action(token, uuid, hostname, action, timeout)
|
||||
|
||||
# Verify the result is None for invalid action
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch('sysinv.api.controllers.v1.vim_api.rest_api_request')
|
||||
def test_vim_host_action_valid_actions(self, mock_rest_api_request):
|
||||
# Test that all valid actions are accepted
|
||||
mock_rest_api_request.return_value = {'status': 'success'}
|
||||
|
||||
token = None
|
||||
uuid = '1be26c0b-03f2-4d2e-ae87-c02d7f33c123'
|
||||
hostname = 'controller-0'
|
||||
timeout = constants.VIM_DEFAULT_TIMEOUT_IN_SECS
|
||||
|
||||
valid_actions = [
|
||||
constants.UNLOCK_ACTION,
|
||||
constants.LOCK_ACTION,
|
||||
constants.FORCE_LOCK_ACTION,
|
||||
constants.FORCE_UNSAFE_LOCK_ACTION,
|
||||
constants.HOST_AUDIT_ACTION
|
||||
]
|
||||
|
||||
for action in valid_actions:
|
||||
result = vim_api.vim_host_action(token, uuid, hostname, action, timeout)
|
||||
self.assertEqual(result, {'status': 'success'})
|
@@ -42,6 +42,7 @@ deps = -r{toxinidir}/requirements.txt
|
||||
-e{[tox]stxdir}/fault/fm-api/source
|
||||
-e{[tox]stxdir}/fault/python-fmclient/fmclient
|
||||
-e{[tox]stxdir}/config/controllerconfig/controllerconfig
|
||||
-e{[tox]stxdir}/config/sysinv/sysinv/sysinv
|
||||
-e{[tox]stxdir}/update/sw-patch/cgcs-patch
|
||||
-e{[tox]stxdir}/utilities/utilities/platform-util/platform-util
|
||||
-e{[tox]stxdir}/utilities/ceph/python-cephclient/python-cephclient
|
||||
|
Reference in New Issue
Block a user