Add command to create, show, delete podmanager
This commit adds cli commands to create, show, delete podmanagers openstack valence podmanager create --help openstack valence podmanager show --help openstack valence podmanager delete --help Example: Create Podmanager request - openstack valence podmanager create <name> <url> --driver <driver> --auth type=<type> --auth username=<username> --auth password=<password> Show Podmanager - openstack valence podmanager show <podm-id> Delete Podmanager - openstack valence podmanager delete <podm-id1>, <podm-id2> Change-Id: Id5262d42232d592ea4de3de20ad88ee5c6aa03b9 Partially-Implements blueprint valenceclient
This commit is contained in:
parent
7f84237263
commit
f16f4a40c2
@ -6,3 +6,4 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
|||||||
oslo.utils>=3.20.0 # Apache-2.0
|
oslo.utils>=3.20.0 # Apache-2.0
|
||||||
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||||
oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
|
oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
|
||||||
|
osc-lib>=1.7.0 # Apache-2.0
|
||||||
|
@ -29,6 +29,9 @@ openstack.cli.extension =
|
|||||||
|
|
||||||
openstack.valence.v1 =
|
openstack.valence.v1 =
|
||||||
valence_podmanager_list = valenceclient.osc.v1.podmanager:ListPodManagers
|
valence_podmanager_list = valenceclient.osc.v1.podmanager:ListPodManagers
|
||||||
|
valence_podmanager_create = valenceclient.osc.v1.podmanager:CreatePodManager
|
||||||
|
valence_podmanager_delete = valenceclient.osc.v1.podmanager:DeletePodManagers
|
||||||
|
valence_podmanager_show = valenceclient.osc.v1.podmanager:ShowPodManager
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
|
@ -15,6 +15,7 @@ testtools>=1.4.0 # MIT
|
|||||||
oslo.utils>=3.20.0 # Apache-2.0
|
oslo.utils>=3.20.0 # Apache-2.0
|
||||||
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0
|
||||||
oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
|
oslo.serialization!=2.19.1,>=1.10.0 # Apache-2.0
|
||||||
|
requests-mock>=1.1.0 # Apache-2.0
|
||||||
|
|
||||||
# releasenotes
|
# releasenotes
|
||||||
reno!=2.3.1,>=1.8.0 # Apache-2.0
|
reno!=2.3.1,>=1.8.0 # Apache-2.0
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
import copy
|
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
@ -125,23 +124,24 @@ class HTTPClient(object):
|
|||||||
def _make_connection_url(self, url):
|
def _make_connection_url(self, url):
|
||||||
return urlparse.urljoin(self.valence_url, url)
|
return urlparse.urljoin(self.valence_url, url)
|
||||||
|
|
||||||
def _http_request(self, url, method, **kwargs):
|
def _http_request(self, conn_url, method, **kwargs):
|
||||||
"""Send an http request with the specified characteristics
|
"""Send an http request with the specified characteristics
|
||||||
|
|
||||||
Wrapper around request.Session.request to handle tasks such as
|
Wrapper around request.Session.request to handle tasks such as
|
||||||
setting headers and error handling.
|
setting headers and error handling.
|
||||||
"""
|
"""
|
||||||
|
kwargs['headers'] = kwargs.get('headers', {})
|
||||||
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
|
||||||
kwargs['headers'].setdefault('User-agent', USER_AGENT)
|
kwargs['headers'].setdefault('User-agent', USER_AGENT)
|
||||||
|
|
||||||
self.log_curl_request(method, url, kwargs)
|
self.log_curl_request(method, conn_url, kwargs)
|
||||||
|
|
||||||
body = kwargs.pop('body', None)
|
body = kwargs.pop('body', None)
|
||||||
if body:
|
headers = kwargs.pop('headers')
|
||||||
kwargs['data'] = body
|
|
||||||
conn_url = self._make_connection_url(url)
|
conn_url = self._make_connection_url(conn_url)
|
||||||
try:
|
try:
|
||||||
resp = self.session.request(method, conn_url, **kwargs)
|
resp = self.session.request(method, conn_url, headers=headers,
|
||||||
|
data=body, json=kwargs)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
msg = (_("Error has occured while handling request for "
|
msg = (_("Error has occured while handling request for "
|
||||||
"%(url)s: %(e)s") % dict(url=conn_url, e=e))
|
"%(url)s: %(e)s") % dict(url=conn_url, e=e))
|
||||||
@ -151,25 +151,25 @@ class HTTPClient(object):
|
|||||||
|
|
||||||
self.log_http_response(resp, resp.text)
|
self.log_http_response(resp, resp.text)
|
||||||
body_iter = six.StringIO(resp.text)
|
body_iter = six.StringIO(resp.text)
|
||||||
|
|
||||||
if resp.status_code >= http_client.BAD_REQUEST:
|
if resp.status_code >= http_client.BAD_REQUEST:
|
||||||
error_json = _extract_error_json(resp.text)
|
error_json = _extract_error_json(resp.text)
|
||||||
raise exc.from_response(resp, error_json.get('faultstring'),
|
raise exc.from_response(resp, error_json.get('faultstring'),
|
||||||
error_json.get('debugfino'), method, url)
|
error_json.get('debugfino'), method,
|
||||||
|
conn_url)
|
||||||
elif resp.status_code in (http_client.FOUND,
|
elif resp.status_code in (http_client.FOUND,
|
||||||
http_client.USE_PROXY):
|
http_client.USE_PROXY):
|
||||||
return self._http_request(resp['location'], method, **kwargs)
|
return self._http_request(resp['location'], method, **kwargs)
|
||||||
|
|
||||||
return resp, body_iter
|
return resp, body_iter
|
||||||
|
|
||||||
def json_request(self, method, url, **kwargs):
|
def json_request(self, method, conn_url, **kwargs):
|
||||||
kwargs.setdefault('headers', {})
|
kwargs.setdefault('headers', {})
|
||||||
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
||||||
kwargs['headers'].setdefault('Accept', 'application/json')
|
kwargs['headers'].setdefault('Accept', 'application/json')
|
||||||
if 'body' in kwargs:
|
if 'body' in kwargs:
|
||||||
kwargs['body'] = jsonutils.dump_as_bytes(kwargs['body'])
|
kwargs['body'] = jsonutils.dump_as_bytes(kwargs['body'])
|
||||||
|
|
||||||
resp, body_iter = self._http_request(url, method, **kwargs)
|
resp, body_iter = self._http_request(conn_url, method, **kwargs)
|
||||||
content_type = resp.headers.get('Content-Type')
|
content_type = resp.headers.get('Content-Type')
|
||||||
if(resp.status_code in (http_client.NO_CONTENT,
|
if(resp.status_code in (http_client.NO_CONTENT,
|
||||||
http_client.RESET_CONTENT)
|
http_client.RESET_CONTENT)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright 2017 Intel, Inc.
|
# Copyright 2017 NEC, Corp.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -13,7 +13,9 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from osc_lib.cli import parseractions
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils
|
from osc_lib import utils
|
||||||
|
|
||||||
|
|
||||||
@ -32,3 +34,107 @@ class ListPodManagers(command.Lister):
|
|||||||
columns = ['uuid', 'name', 'url', 'driver', 'status', 'created_at',
|
columns = ['uuid', 'name', 'url', 'driver', 'status', 'created_at',
|
||||||
'updated_at']
|
'updated_at']
|
||||||
return (columns, (utils.get_dict_properties(s, columns) for s in obj))
|
return (columns, (utils.get_dict_properties(s, columns) for s in obj))
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePodManager(command.ShowOne):
|
||||||
|
_description = "Creates new podmanager"
|
||||||
|
auth_required = False
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(CreatePodManager, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'name',
|
||||||
|
metavar='<name>',
|
||||||
|
help=('Name for the PodManager'))
|
||||||
|
parser.add_argument(
|
||||||
|
'url',
|
||||||
|
metavar='<url>',
|
||||||
|
help=('URL of the PodManager'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--driver',
|
||||||
|
metavar='<driver>',
|
||||||
|
default='redfishv1',
|
||||||
|
help=("PodManager driver, default is 'redfishv1'"))
|
||||||
|
parser.add_argument(
|
||||||
|
'--auth',
|
||||||
|
metavar='<key=value>',
|
||||||
|
required=True,
|
||||||
|
action=parseractions.KeyValueAction,
|
||||||
|
help=("auth information to connect to podmanager, repeat option "
|
||||||
|
"to set each key. Accepted keys are type, username, password"
|
||||||
|
"If type not specified 'basic' is taken by default"))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug("take_action(%s)", parsed_args)
|
||||||
|
auth = parsed_args.auth
|
||||||
|
auth['type'] = auth.get('type', 'basic')
|
||||||
|
|
||||||
|
req = {
|
||||||
|
'name': parsed_args.name,
|
||||||
|
'url': parsed_args.url,
|
||||||
|
'driver': parsed_args.driver,
|
||||||
|
'authentication': [{'type': auth['type'],
|
||||||
|
'auth_items': {
|
||||||
|
'username': auth['username'],
|
||||||
|
'password': auth['password']}}]
|
||||||
|
}
|
||||||
|
|
||||||
|
client = self.app.client_manager.valence
|
||||||
|
obj = client.create_podmanager(req)
|
||||||
|
columns = ('uuid', 'name', 'url', 'driver', 'status', 'created_at',
|
||||||
|
'updated_at')
|
||||||
|
return (columns, (utils.get_dict_properties(obj, columns)))
|
||||||
|
|
||||||
|
|
||||||
|
class DeletePodManagers(command.Command):
|
||||||
|
_description = "Delete podmanagers"
|
||||||
|
auth_required = False
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(DeletePodManagers, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id',
|
||||||
|
nargs='+',
|
||||||
|
metavar='<id>',
|
||||||
|
help=('Podmanagers id(s) to delete'))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug("take_action(%s)", parsed_args)
|
||||||
|
client = self.app.client_manager.valence
|
||||||
|
count = 0
|
||||||
|
for p in parsed_args.id:
|
||||||
|
try:
|
||||||
|
client.delete_podmanager(p)
|
||||||
|
except Exception as e:
|
||||||
|
count = count + 1
|
||||||
|
self.log.error("podmanager %s deletion failed with error %s",
|
||||||
|
p, str(e))
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
|
total = len(parsed_args.id)
|
||||||
|
msg = (("%(result)s of %(total)s podmanagers(s) "
|
||||||
|
"failed to delete.") % {'result': count, 'total': total})
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class ShowPodManager(command.ShowOne):
|
||||||
|
_description = "Show podmanager"
|
||||||
|
auth_required = False
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ShowPodManager, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id',
|
||||||
|
metavar='<id>',
|
||||||
|
help=('Podmanager id to show'))
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
self.log.debug("take_action(%s)", parsed_args)
|
||||||
|
client = self.app.client_manager.valence
|
||||||
|
obj = client.show_podmanager(parsed_args.id)
|
||||||
|
columns = ('uuid', 'name', 'url', 'driver', 'status', 'created_at',
|
||||||
|
'updated_at')
|
||||||
|
return (columns, (utils.get_dict_properties(obj, columns)))
|
||||||
|
@ -114,12 +114,12 @@ class HttpClientTest(utils.BaseTestCase):
|
|||||||
|
|
||||||
client.json_request('GET', 'redfish/v1')
|
client.json_request('GET', 'redfish/v1')
|
||||||
|
|
||||||
def test_server_http_not_valide_request(self):
|
def test_server_http_not_valid_request(self):
|
||||||
kwargs = {"valence_url": "http://localhost/"}
|
kwargs = {"valence_url": "http://localhost/"}
|
||||||
client = http.HTTPClient(**kwargs)
|
client = http.HTTPClient(**kwargs)
|
||||||
client.session.request = mock.Mock(
|
client.session.request = mock.Mock(
|
||||||
side_effect=http.requests.exceptions.InvalidSchema)
|
side_effect=http.requests.exceptions.InvalidSchema)
|
||||||
self.assertRaises(exc.ValidationError, client._http_request, 'GET',
|
self.assertRaises(exc.ValidationError, client.json_request, 'GET',
|
||||||
'http://localhost/')
|
'http://localhost/')
|
||||||
|
|
||||||
@mock.patch.object(http.LOG, 'debug', autospec=True)
|
@mock.patch.object(http.LOG, 'debug', autospec=True)
|
||||||
@ -149,7 +149,7 @@ class HttpClientTest(utils.BaseTestCase):
|
|||||||
with mock.patch.object(client, 'session',
|
with mock.patch.object(client, 'session',
|
||||||
autospec=True) as mock_session:
|
autospec=True) as mock_session:
|
||||||
mock_session.request.side_effect = iter([resp])
|
mock_session.request.side_effect = iter([resp])
|
||||||
response, body_iter = client._http_request('/redfish/v1/Nodes',
|
response, body_iter = client.json_request('GET',
|
||||||
'GET')
|
'/redfish/v1/Nodes')
|
||||||
|
|
||||||
self.assertEqual(http_client.OK, response.status_code)
|
self.assertEqual(http_client.OK, response.status_code)
|
||||||
|
0
valenceclient/tests/unit/osc/__init__.py
Normal file
0
valenceclient/tests/unit/osc/__init__.py
Normal file
28
valenceclient/tests/unit/osc/test_base.py
Normal file
28
valenceclient/tests/unit/osc/test_base.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# 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 argparse
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from osc_lib.tests import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestValenceClient(utils.TestCommand):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestValenceClient, self).setUp()
|
||||||
|
self.namespace = argparse.Namespace()
|
||||||
|
self.app.client_manager.session = mock.Mock()
|
||||||
|
self.app.client_manager.valence = mock.Mock()
|
||||||
|
self.valenceclient = self.app.client_manager.valence
|
||||||
|
self.addCleanup(mock.patch.stopall)
|
92
valenceclient/tests/unit/osc/test_podmanager.py
Normal file
92
valenceclient/tests/unit/osc/test_podmanager.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Copyright 2017 NEC, Corp.
|
||||||
|
# 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 valenceclient.osc.v1 import podmanager
|
||||||
|
from valenceclient.tests.unit.osc import test_base
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreatePodmanager(test_base.TestValenceClient):
|
||||||
|
def test_create_podmanager(self):
|
||||||
|
response = {"uuid": "test-id",
|
||||||
|
"name": "test-podm",
|
||||||
|
"url": "http://localhost",
|
||||||
|
"driver": "redfishv1",
|
||||||
|
"status": "Offline",
|
||||||
|
"created_at": "2017-08-07 06:56:34 UTC",
|
||||||
|
"updated_at": "2017-08-28 06:56:34 UTC"}
|
||||||
|
|
||||||
|
arglist = ['test-podm', 'http://localhost',
|
||||||
|
'--auth', "username=user",
|
||||||
|
'--auth', "password=pass"]
|
||||||
|
verifylist = [
|
||||||
|
('name', 'test-podm'),
|
||||||
|
('url', 'http://localhost'),
|
||||||
|
('auth', {'username': 'user', 'password': 'pass'}),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
mocker = mock.Mock(return_value=response)
|
||||||
|
self.valenceclient.create_podmanager = mocker
|
||||||
|
cmd = podmanager.CreatePodManager(self.app, self.namespace)
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = list(cmd.take_action(parsed_args))
|
||||||
|
filtered = [('uuid', 'name', 'url', 'driver', 'status', 'created_at',
|
||||||
|
'updated_at'),
|
||||||
|
('test-id', 'test-podm', 'http://localhost', 'redfishv1',
|
||||||
|
'Offline', '2017-08-07 06:56:34 UTC',
|
||||||
|
'2017-08-28 06:56:34 UTC')]
|
||||||
|
self.assertEqual(filtered, result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestShowPolicy(test_base.TestValenceClient):
|
||||||
|
def test_show_policy(self):
|
||||||
|
podmanager_id = "test-id"
|
||||||
|
arglist = [podmanager_id]
|
||||||
|
verifylist = [
|
||||||
|
('id', podmanager_id),
|
||||||
|
]
|
||||||
|
response = {"uuid": "test-id",
|
||||||
|
"name": "test-podm",
|
||||||
|
"url": "http://localhost",
|
||||||
|
"driver": "redfishv1",
|
||||||
|
"status": "Offline",
|
||||||
|
"created_at": "2017-08-07 06:56:34 UTC",
|
||||||
|
"updated_at": "2017-08-28 06:56:34 UTC"}
|
||||||
|
|
||||||
|
mocker = mock.Mock(return_value=response)
|
||||||
|
self.valenceclient.show_podmanager = mocker
|
||||||
|
cmd = podmanager.ShowPodManager(self.app, self.namespace)
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = list(cmd.take_action(parsed_args))
|
||||||
|
filtered = [('uuid', 'name', 'url', 'driver', 'status', 'created_at',
|
||||||
|
'updated_at'),
|
||||||
|
('test-id', 'test-podm', 'http://localhost', 'redfishv1',
|
||||||
|
'Offline', '2017-08-07 06:56:34 UTC',
|
||||||
|
'2017-08-28 06:56:34 UTC')]
|
||||||
|
self.assertEqual(filtered, result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeletePodmanager(test_base.TestValenceClient):
|
||||||
|
def test_delete_podmanager(self):
|
||||||
|
arglist = ['test-id']
|
||||||
|
verifylist = [('id', ['test-id']), ]
|
||||||
|
mocker = mock.Mock(return_value=None)
|
||||||
|
self.valenceclient.delete_podmanager = mocker
|
||||||
|
cmd = podmanager.DeletePodManagers(self.app, self.namespace)
|
||||||
|
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||||
|
result = cmd.take_action(parsed_args)
|
||||||
|
mocker.assert_called_with('test-id')
|
||||||
|
self.assertIsNone(result)
|
@ -18,11 +18,27 @@ from valenceclient.common import http
|
|||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
|
|
||||||
podmanager_path = "/v1/pod_managers"
|
podmanagers = "/v1/pod_managers"
|
||||||
|
podmanager_path = "/v1/pod_managers/%s"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.http_client = http.HTTPClient(**kwargs)
|
self.http_client = http.HTTPClient(**kwargs)
|
||||||
|
|
||||||
def list_podmanagers(self):
|
def list_podmanagers(self):
|
||||||
resp, body = self.http_client.json_request('get', self.podmanager_path)
|
resp, body = self.http_client.json_request('get', self.podmanagers)
|
||||||
|
return body
|
||||||
|
|
||||||
|
def create_podmanager(self, request):
|
||||||
|
resp, body = self.http_client.json_request('post', self.podmanagers,
|
||||||
|
**request)
|
||||||
|
return body
|
||||||
|
|
||||||
|
def delete_podmanager(self, id):
|
||||||
|
resp, body = self.http_client.json_request('delete',
|
||||||
|
self.podmanager_path % id)
|
||||||
|
return body
|
||||||
|
|
||||||
|
def show_podmanager(self, id):
|
||||||
|
resp, body = self.http_client.json_request('get',
|
||||||
|
self.podmanager_path % id)
|
||||||
return body
|
return body
|
||||||
|
Loading…
Reference in New Issue
Block a user