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:
Anusha Ramineni 2017-08-25 10:58:50 +05:30
parent 7f84237263
commit f16f4a40c2
10 changed files with 267 additions and 20 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)))

View File

@ -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)

View File

View 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)

View 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)

View File

@ -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