{"id":752,"date":"2020-04-10T11:26:04","date_gmt":"2020-04-10T11:26:04","guid":{"rendered":"https:\/\/spoton.cz\/?p=752"},"modified":"2020-04-10T11:26:04","modified_gmt":"2020-04-10T11:26:04","slug":"hp-primera-api-api-v3-and-api-v1","status":"publish","type":"post","link":"https:\/\/spoton.cz\/index.php\/2020\/04\/10\/hp-primera-api-api-v3-and-api-v1\/","title":{"rendered":"HP Primera API. API v3 and API v1"},"content":{"rendered":"\n<p>At the beginning of the journey to conquer the API of HPe Primera storages was a simple task. Create a CLI monitoring tool that could send information do different ticketing and monitoring tools. One would think this is an easy task. Let&#8217;s take a look at why this is not so easy and also, how to make it fairly easy.<\/p>\n\n\n\n<p>Why is APIv1 crap? How to use APIv3? Why did I choose that super sexy green featured image? I&#8217;ll try to answer some of these. <\/p>\n\n\n\n<p>I&#8217;ll look at a 3 inch long stick, that should bring down an airplane (SNMP), will bisect the APIv1 (and kill it in the process) and eventually will go down the route of implementing the APIv3, which is the most capable, yet HP will not let you use it &#8211; officially (used by the GUI).<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2>First things first<\/h2>\n\n\n\n<p>Let&#8217;s get rid of the obious things, that will bugger you most of the time. <\/p>\n\n\n\n<ul><li>HPe Primera is reporting as a 3PAR <\/li><li>is labeled as a 3PAR<\/li><li>uses 3PAR MIBs<\/li><li>needs SSMC (3PAR) to do the initial setup<\/li><li>The super sexy green image was chosen as  it was the only high-res HP related image \ud83d\ude42<\/li><\/ul>\n\n\n\n<h2>Case one: Official documentation and API v1<\/h2>\n\n\n\n<p>According to the official documentation, the way to authenticate and pull data is as follows (in Python3):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def login(user, password):\n    print(\"Login\")\n    info = {'user': user, 'password': password}\n    headers = {'Content-Type': 'application\/json'}\n    body = requests.post(f\"{_API_PATH}\/credentials\",\n                         data=json.dumps(info),\n                         headers=headers,\n                         verify=False)\n    result = json.loads(body.text)\n    pprint(result)\n    if 'key' in result.keys():\n        print(\"Login Successfull\")\n        return result['key']\n    else:\n        print(\"Login failed\")\n        return False\n\ndef logout(session_key):\n    print('Logout')\n    body = requests.delete(f'{_API_PATH}\/credentials\/{session_key}',\n                           verify=False)\n    if body.status_code == 200:\n        print(\"Logout successfull\")\n        return True\n    else:\n        return False\n\n# Let's set the variables\n_API_APPEND = \"api\/v1\"\n_PORT = \"443\"\n_SYSTEM = \"primera.internal\"\n_API_PATH = f\"https:\/\/{_SYSTEM}:{_PORT}\/{_API_APPEND}\"\n_USER='monitor'\n_PASSWORD='monitor'\n# And the call:\nif '__main__':\n    try:\n        session = login(_USER, _PASSWORD)\n    except:\n        SystemExit(1)\n<\/pre>\n\n\n\n<p>The result is OK, no problems here. <\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ python alerts_list.py \nLogin\n{'key': '0-d0b3423b42608483b50b65dd515b094d-1154905e'}\nLogin Successfull\nLogout\nLogout successful<\/pre>\n\n\n\n<p>The problem starts with a more sophisticated call, namely I want to pull the alerts, currently present onthe system. So according to the documentation, I do:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def get_alerts(session_key, minutes):\n    print('Get Alerts')\n    headers = {'Content-Type': 'application\/json'}\n    headers.update({'x-hp3par-wsapi-sessionkey': session_key})\n    query = f'\/minutes:{minutes}'\n    body = requests.get(f'{_API_PATH}\/eventlog{query}',\n                           verify=False, headers=headers)\n    result = json.loads(body.text)\n    pprint(result)\n    if body.status_code == 200:\n        return result\n    elif body.status_code == 500:\n        print(\"Internal error\")\n        return False\n    elif body.status_code == 400:\n        print(\"Bad request\")\n        return False\n    else:\n        return False\n\nalerts = get_alerts(session, 144000)<\/pre>\n\n\n\n<p>And voila, we get alerts<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">{'members': &#91;], 'total': 0}<\/pre>\n\n\n\n<p>Oh, wait&#8230; maybe the time range is wrong, let&#8217;s put in some really big number, like 144000 (100 days)<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Login\n{'key': '0-c38bd7c83f453a02a2c868c67bf8589c-9055905e'}\nLogin Successfull\nGet Alerts\n{'members': &#91;{'alertInfo': {'alertId': '26', 'messageCode': '0x00300de'},\n              'category': 2,\n              'class': 1,\n              'component': 3,\n              'componentId': '2:3:1',\n              'components': 'sw_port:2:3:1',\n              'description': 'Port 2:3:1 Degraded (Target Mode Port Went '\n                             'Offline {0x3})',\n              'id': '7930925',\n              'isDataChanged': True,\n              'links': &#91;{'href': 'https:\/\/primera.internal\/api\/v1\/ports\/2:3:1',\n                         'rel': 'port'}],\n              'resource': 3,\n              'resourceId': '2:3:1',\n              'severity': 5,\n              'time': '2020-03-27T10:34:10+01:00',\n              'timeSecs': 1585301650,\n              'type': 'Component state change'},\n\n...\n,\n 'total': 65}\nLogout\nLogout successfull\n\nreal\t0m59,552s\nuser\t0m0,512s\nsys\t0m0,071s<\/pre>\n\n\n\n<p>oh, how cool. We&#8217;ve got alerts. I will not get in deep with how the contents of these alerts is different from what you see in the console, but let&#8217;s take a look at the more acute problem: The<strong> TIME IT TAKES?!<\/strong> &#8211; honestly 60 seconds to query a DB is way too much. There are a couple things I have taken from the time I spent with the API and alerting<\/p>\n\n\n\n<ul><li>MINUS: These alerts do NOT show up immediately, god knows why &#8211; tested several times<\/li><li>MINUS: The time it takes to list is just too long<\/li><li>MINUS: Different to what you get from CLI \/ GUI<\/li><li>PLUS: Is documented<\/li><\/ul>\n\n\n\n<p><strong>And therefore, APIv1 is crap (So far)<\/strong><\/p>\n\n\n\n<p>Luckily, there is a solution. As the GUI uses an API as well, data can be pulled via that interface, right? Plus you&#8217;ll get all HP Primera data you already can see in the GUI, guaranteed. Although, it iwll be slightly different from the CLI version.<\/p>\n\n\n\n<h2>A new dawn, API v3, but undocumented<\/h2>\n\n\n\n<p>Let&#8217;s start with the list from above. Pluses and minuses.<\/p>\n\n\n\n<ul><li>PLUS: Kind of real-time<\/li><li>PLUS: Near instant retrieval of data<\/li><li>PLUS: GUI uses the same API, so&#8230; == GUI<\/li><li>MINUS: Documentation<\/li><li>Also, uses a more modern way of securing, with session cookies<\/li><\/ul>\n\n\n\n<p>I&#8217;ve done this the &#8220;reverse engineering&#8221; way, so please if you find anything incorrect, wrong, or plain bullshit, feel free to let me know.<\/p>\n\n\n\n<h3>Login and logout APIv3 on the Primera from HPe<\/h3>\n\n\n\n<p>There are a couple things that needed changing in the login process. First of all, you really need to specify the password in an array, or list (python). The API path has also changed.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">_API_APPEND = \"\/api\/v3\"\n_PORT = \"443\"\n_SYSTEM = \"primera.internal\"\n_API_PATH = f\"https:\/\/{_SYSTEM}:{_PORT}{_API_APPEND}\"\n_USER='monitor'\n_PASSWORD=['m','o','n','i','t','o','r']<\/pre>\n\n\n\n<p>Set up a helper function is a good idea, I thoght.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def check_api_result(api_res_body):\n    if api_res_body.status_code == 200:\n        return True\n    elif api_res_body.status_code == 500:\n        print(\"ERROR: Internal error\")\n        return False\n    elif api_res_body.status_code == 400:\n        print(\"ERROR: Bad request\")\n        return False\n    else:\n        return False<\/pre>\n\n\n\n<p>Second, I added the session cookie value into the session response<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def login(user, password):\n    ''' Login and get session token\n\n    '''\n    print(\"Login\")\n    info = {'user': user, 'password': password}\n    headers = {'Content-Type': 'application\/json'}\n    body = requests.post(f\"{_API_PATH}\/credentials\",\n                         data=json.dumps(info),\n                         headers=headers,\n                         verify=False)\n    result = json.loads(body.text)\n    if 'key' in result.keys():\n        session_token = body.cookies['sessionToken']\n        return {'key': result['key'], 'token': session_token}\n    else:\n        return False<\/pre>\n\n\n\n<p>The logout is also a little different to the original.  After we delete the session on the credentials endpoint, we&#8217;ll check for the validity of the session once again. Logout is only valid of the session returns an error.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def logout(session_key):\n    ''' Logout == invalidate the session token\n\n    '''\n    print('Logout')\n    headers = {'Content-Type': 'application\/json'}\n    headers.update({'x-hp3par-wsapi-sessionkey': session_key['key']})\n    jar = requests.cookies.RequestsCookieJar()\n    jar.set('sessionToken', session_key['token'])\n    body = requests.delete(f\"{_API_PATH}\/credentials\",\n                           verify=False, headers=headers, cookies=jar)\n    check_session = requests.get(f\"{_API_PATH}\/credentials\",\n                           verify=False, headers=headers, cookies=jar)\n    if check_api_result(body) and check_session.status_code == 401:\n        return True\n    else:\n        return False<\/pre>\n\n\n\n<p>Now, whenever we call an endpoint from the API, we need to include a cookie along with the other data. Take an example from the alerts<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def get_alerts(session_key):\n    ''' Getting the alert objects from HP Primera\n\n    '''\n    print('Get Alerts')\n    headers = {'Content-Type': 'application\/json'}\n    headers.update({'Cache-control': 'no-cache'})\n    jar = requests.cookies.RequestsCookieJar()\n    jar.set('sessionToken', session_key['token'])\n    body = requests.get(f\"{_API_PATH}\/alerts\",\n        verify=False, headers=headers, cookies=jar)\n    result = json.loads(body.text)\n    if check_api_result(body):\n        return result\n    else:\n        return False<\/pre>\n\n\n\n<p>Of course, there are better ways of calling this, constructing it into classes, building modules, but this was a quick and dirty way of proving the concept. Now a quick look at what we&#8217;ve accomplished:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">if '__main__':\n    _print = True\n    session = login(_USER, _PASSWORD)\n    if session:\n        print(\"Login Successfull\")\n        alerts = get_alerts(session)\n        pprint(alerts)\n    else:\n        # Login failed\n        print(\"Login failed\")\n        SystemExit(1)\n    if logout(session):\n        print(\"Logout successfull\")\n    else:\n        # Logout failed\n        print(\"Logout failed\")\n        SystemExit(2)<\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">Login\n Login Successfull\n Get Alerts\n {'members': {'0006049a8f20fa494e82c88c42e17a8b': {'action': None,\n                                                   'associatedResource': {'category': 'systems',\n                                                                          'searchName': 'wwn',\n                                                                          'searchValue': '2FF70002AC02524F'},\n                                                   'caseDetails': None,\n                                                   'count': 1,\n...\n Logout\n Logout successfull\n real    0m3,707s\n user    0m1,366s\n sys    0m0,201s<\/pre>\n\n\n\n<p>3 seconds. HP, honestly? APIv1? Really?<\/p>\n\n\n\n<p>Of course, there are some things you need to take care of, such as the alert origin time vs. last occurrence. In other words, <strong>if an alert is new<\/strong>, there will only be a value of {&#8216;members&#8217;: {id: {&#8216;originEvent&#8217;: &#8216;firstTime&#8217;}}}. While if it is a <strong>recurring event \/ alert<\/strong>, there is an additional field in the same originEvent, fileld with data and name &#8216;lastTime&#8217;. So just take it into account. <\/p>\n\n\n\n<h2>Why not go directly with the SNMP instead?<\/h2>\n\n\n\n<p>You might not want to, or cannot, as all the other monitoring is based on different protocols. The major problem is: there is no simple solution to the SNMP setup. Each alert will generate a clearing event, as well as state change event, as well as a trap event. In this regard, you really need to implement the whole stack of alerting and reporting, INCLUDING the clearing and state change events. Otherwise, you&#8217;ll end up with a lot of traps.<\/p>\n\n\n\n<p>Most of the SNMP capable devices also support polling. I&#8217;ve not been able to find any documentation, or the configuration capabilities on the primera itself to support this functionality. Have not tried it &#8211; as I suspect &#8216;time lost on this&#8217;<\/p>\n\n\n\n<p>Thx<\/p>\n\n\n\n<p><strong>Epilogue: <\/strong><\/p>\n\n\n\n<p>Milan Toman is a Storage engineer by trade and has shared the common path with HP(e) products since 2008, including the early adoption of Blade technology, EVAs, MSAs, XPs(which are Hitachi), remembers 3PAR as 3PAR and ultimately hacks on whatever he can get his hands on.  <\/p>\n","protected":false},"excerpt":{"rendered":"<p>At the beginning of the journey to conquer the API of HPe Primera storages was a simple task. Create a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":753,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[4,5,7,1],"tags":[18,19,21,41,52,53,54,55,82,83,84,87,93],"_links":{"self":[{"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/posts\/752"}],"collection":[{"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/comments?post=752"}],"version-history":[{"count":0,"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/posts\/752\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/media\/753"}],"wp:attachment":[{"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/media?parent=752"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/categories?post=752"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/spoton.cz\/index.php\/wp-json\/wp\/v2\/tags?post=752"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}