├── VERSION ├── tests ├── __init__.py ├── unit │ ├── __init__.py │ ├── test_common.py │ ├── test_exception.py │ ├── test_node.py │ ├── test_http.py │ ├── test_token_request.py │ ├── test_ecsclient.py │ └── test_authentication.py ├── functional │ ├── test_whoami.py │ ├── test_node.py │ ├── test_license.py │ ├── test_alerts.py │ ├── test_capacity.py │ ├── test_vdc_keystore.py │ ├── test_certificate.py │ ├── test_tenant.py │ ├── test_vdc.py │ ├── test_namespace.py │ ├── __init__.py │ ├── test_data_store.py │ ├── test_storage_pool.py │ ├── helper.py │ ├── test_object_user.py │ ├── test_management_user.py │ ├── test_password_group.py │ ├── test_secret_key.py │ └── test_replication_group.py └── sample.conf ├── docs ├── .gitignore ├── source │ ├── _static │ │ └── .keep │ ├── _templates │ │ └── .keep │ ├── index.rst │ └── conf.py ├── Makefile └── make.bat ├── ecsclient ├── __init__.py ├── common │ ├── __init__.py │ ├── cas │ │ └── __init__.py │ ├── metering │ │ └── __init__.py │ ├── other │ │ ├── __init__.py │ │ └── user_info.py │ ├── support │ │ ├── __init__.py │ │ └── call_home.py │ ├── configuration │ │ ├── __init__.py │ │ ├── feature.py │ │ ├── certificate.py │ │ ├── licensing.py │ │ └── configuration_properties.py │ ├── monitoring │ │ ├── __init__.py │ │ ├── capacity.py │ │ ├── alerts.py │ │ └── events.py │ ├── multitenancy │ │ ├── __init__.py │ │ └── tenant.py │ ├── provisioning │ │ ├── __init__.py │ │ ├── node.py │ │ ├── vdc_keystore.py │ │ ├── storage_pool.py │ │ └── base_url.py │ ├── file_system_access │ │ ├── __init__.py │ │ └── nfs.py │ ├── geo_replication │ │ ├── __init__.py │ │ └── temporary_failed_zone.py │ ├── user_management │ │ ├── __init__.py │ │ ├── authentication_provider.py │ │ ├── password_group.py │ │ ├── management_user.py │ │ ├── secret_key.py │ │ └── object_user.py │ ├── util.py │ ├── exceptions.py │ └── token_request.py ├── v2 │ ├── __init__.py │ ├── support │ │ └── __init__.py │ ├── cas │ │ └── __init__.py │ ├── metering │ │ └── __init__.py │ ├── other │ │ └── __init__.py │ ├── multitenancy │ │ └── __init__.py │ ├── monitoring │ │ └── __init__.py │ ├── geo_replication │ │ └── __init__.py │ ├── configuration │ │ └── __init__.py │ ├── user_management │ │ └── __init__.py │ ├── provisioning │ │ └── __init__.py │ └── client.py ├── v3 │ ├── __init__.py │ ├── cas │ │ ├── __init__.py │ │ └── cas.py │ ├── support │ │ └── __init__.py │ ├── metering │ │ └── __init__.py │ ├── other │ │ └── __init__.py │ ├── multitenancy │ │ └── __init__.py │ ├── monitoring │ │ └── __init__.py │ ├── geo_replication │ │ └── __init__.py │ ├── configuration │ │ ├── __init__.py │ │ ├── syslog.py │ │ └── snmp.py │ ├── provisioning │ │ ├── __init__.py │ │ └── node.py │ ├── user_management │ │ └── __init__.py │ └── client.py ├── v4 │ ├── __init__.py │ ├── cas │ │ ├── __init__.py │ │ └── cas.py │ ├── support │ │ └── __init__.py │ ├── metering │ │ └── __init__.py │ ├── other │ │ └── __init__.py │ ├── multitenancy │ │ └── __init__.py │ ├── monitoring │ │ └── __init__.py │ ├── geo_replication │ │ └── __init__.py │ ├── configuration │ │ ├── __init__.py │ │ ├── syslog.py │ │ └── snmp.py │ ├── provisioning │ │ ├── __init__.py │ │ └── node.py │ ├── user_management │ │ └── __init__.py │ └── client.py ├── client.py ├── authentication.py └── baseclient.py ├── requirements.txt ├── MANIFEST.in ├── setup.cfg ├── test-requirements.txt ├── .gitignore ├── .overcommit.yml ├── tox.ini ├── LICENSE ├── .travis.yml └── setup.py /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.12 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /ecsclient/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/_static/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/source/_templates/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v3/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v3/cas/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v4/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v4/cas/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/cas/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v2/support/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v3/support/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v4/support/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/metering/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/other/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/support/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/monitoring/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/multitenancy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/provisioning/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/file_system_access/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/geo_replication/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/common/user_management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecsclient/v2/cas/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.cas import cas 2 | 3 | cas = cas 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.18.4,<3.0 2 | six>=1.11.0,<2.0 3 | jsonschema>=2.6.0,<3.0 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include VERSION 2 | include requirements.txt 3 | include test-requirements.txt 4 | include README.rst -------------------------------------------------------------------------------- /ecsclient/v2/metering/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.metering import billing 2 | 3 | billing = billing 4 | -------------------------------------------------------------------------------- /ecsclient/v2/other/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.other import user_info 2 | 3 | user_info = user_info 4 | -------------------------------------------------------------------------------- /ecsclient/v3/metering/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.metering import billing 2 | 3 | billing = billing 4 | -------------------------------------------------------------------------------- /ecsclient/v3/other/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.other import user_info 2 | 3 | user_info = user_info 4 | -------------------------------------------------------------------------------- /ecsclient/v4/metering/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.metering import billing 2 | 3 | billing = billing 4 | -------------------------------------------------------------------------------- /ecsclient/v4/other/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.other import user_info 2 | 3 | user_info = user_info 4 | -------------------------------------------------------------------------------- /ecsclient/v2/multitenancy/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.multitenancy import namespace 2 | 3 | namespace = namespace 4 | -------------------------------------------------------------------------------- /ecsclient/v3/multitenancy/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.multitenancy import namespace 2 | 3 | namespace = namespace 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | where=tests 3 | nocapture=1 4 | cover-package=ecsclient 5 | cover-erase=1 6 | 7 | [metadata] 8 | description-file=README.rst 9 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | flake8==3.2.1 2 | mock==2.0.0 3 | nose==1.3.7 4 | coverage==4.3.4 5 | tox==2.6.0 6 | testtools==2.2.0 7 | requests-mock[fixture]==1.3.0 -------------------------------------------------------------------------------- /ecsclient/common/file_system_access/nfs.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class NFS(object): 7 | # TODO: Implement it 8 | pass 9 | -------------------------------------------------------------------------------- /ecsclient/common/support/call_home.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class CallHome(object): 7 | # TODO: Implement it 8 | pass 9 | -------------------------------------------------------------------------------- /ecsclient/v4/multitenancy/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.multitenancy import namespace 2 | from ecsclient.common.multitenancy import tenant 3 | 4 | namespace = namespace 5 | tenant = tenant 6 | -------------------------------------------------------------------------------- /ecsclient/v2/monitoring/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.monitoring import capacity, dashboard, events, alerts 2 | 3 | capacity = capacity 4 | dashboard = dashboard 5 | events = events 6 | alerts = alerts 7 | -------------------------------------------------------------------------------- /ecsclient/v3/monitoring/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.monitoring import capacity, dashboard, events, alerts 2 | 3 | capacity = capacity 4 | dashboard = dashboard 5 | events = events 6 | alerts = alerts 7 | -------------------------------------------------------------------------------- /ecsclient/v4/monitoring/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.monitoring import capacity, dashboard, events, alerts 2 | 3 | capacity = capacity 4 | dashboard = dashboard 5 | events = events 6 | alerts = alerts 7 | -------------------------------------------------------------------------------- /ecsclient/v2/geo_replication/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.geo_replication import replication_group, temporary_failed_zone 2 | 3 | replication_group = replication_group 4 | temporary_failed_zone = temporary_failed_zone 5 | -------------------------------------------------------------------------------- /ecsclient/v3/geo_replication/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.geo_replication import replication_group, temporary_failed_zone 2 | 3 | replication_group = replication_group 4 | temporary_failed_zone = temporary_failed_zone 5 | -------------------------------------------------------------------------------- /ecsclient/v4/geo_replication/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.geo_replication import replication_group, temporary_failed_zone 2 | 3 | replication_group = replication_group 4 | temporary_failed_zone = temporary_failed_zone 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | Vagrantfile 4 | .vagrant/ 5 | *.log 6 | .idea/ 7 | *.pyc 8 | .tox/ 9 | *.egg-info/ 10 | .coverage 11 | htmlcov/ 12 | build/ 13 | dist/ 14 | test.conf 15 | .venv/ 16 | .direnv/ 17 | .envrc 18 | *.lic 19 | -------------------------------------------------------------------------------- /ecsclient/v2/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.configuration import certificate, configuration_properties, licensing, feature 2 | 3 | certificate = certificate 4 | configuration_properties = configuration_properties 5 | licensing = licensing 6 | feature = feature 7 | -------------------------------------------------------------------------------- /ecsclient/v3/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.configuration import certificate, configuration_properties, licensing, feature 2 | 3 | certificate = certificate 4 | configuration_properties = configuration_properties 5 | licensing = licensing 6 | feature = feature 7 | -------------------------------------------------------------------------------- /ecsclient/v4/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.configuration import certificate, configuration_properties, licensing, feature 2 | 3 | certificate = certificate 4 | configuration_properties = configuration_properties 5 | licensing = licensing 6 | feature = feature 7 | -------------------------------------------------------------------------------- /tests/functional/test_whoami.py: -------------------------------------------------------------------------------- 1 | from ecsclient import schemas 2 | from tests import functional 3 | 4 | 5 | class TestWhoami(functional.BaseTestCase): 6 | def test_whoami(self): 7 | response = self.client.user_info.whoami() 8 | self.assertValidSchema(response, schemas.WHOAMI) 9 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | PreCommit: 2 | 3 | PythonFlake8: 4 | enabled: true 5 | on_warn: fail 6 | 7 | TrailingWhitespace: 8 | enabled: true 9 | 10 | PrePush: 11 | 12 | PythonNose: 13 | enabled: true 14 | description: 'Run nose test suite' 15 | required_executable: 'nosetests' -------------------------------------------------------------------------------- /tests/functional/test_node.py: -------------------------------------------------------------------------------- 1 | from ecsclient import schemas 2 | from tests import functional 3 | 4 | 5 | class TestNode(functional.BaseTestCase): 6 | 7 | def test_node_list(self): 8 | response = self.client.node.list() 9 | self.assertValidSchema(response, schemas.NODE_LIST) 10 | -------------------------------------------------------------------------------- /ecsclient/v2/user_management/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.user_management import authentication_provider, secret_key, \ 2 | management_user, object_user 3 | 4 | authentication_provider = authentication_provider 5 | secret_key = secret_key 6 | management_user = management_user 7 | object_user = object_user 8 | -------------------------------------------------------------------------------- /tests/sample.conf: -------------------------------------------------------------------------------- 1 | [func_test] 2 | # Sample config 3 | # Functional test account (needs admin access to the account). 4 | token_endpoint = https://10.0.1.1:4443/login 5 | ecs_endpoint = https://10.0.1.1:4443 6 | username = root 7 | password = ChangeMe 8 | api_version = 3 9 | license_file = /home/user/license.lic 10 | -------------------------------------------------------------------------------- /ecsclient/v3/provisioning/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.provisioning import base_url, bucket, data_store, storage_pool, virtual_data_center, vdc_keystore 2 | 3 | base_url = base_url 4 | bucket = bucket 5 | data_store = data_store 6 | storage_pool = storage_pool 7 | virtual_data_center = virtual_data_center 8 | vdc_keystore = vdc_keystore 9 | -------------------------------------------------------------------------------- /ecsclient/v4/provisioning/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.provisioning import base_url, bucket, data_store, storage_pool, virtual_data_center, vdc_keystore 2 | 3 | base_url = base_url 4 | bucket = bucket 5 | data_store = data_store 6 | storage_pool = storage_pool 7 | virtual_data_center = virtual_data_center 8 | vdc_keystore = vdc_keystore 9 | -------------------------------------------------------------------------------- /ecsclient/v3/user_management/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.user_management import authentication_provider, secret_key, \ 2 | password_group, management_user, object_user 3 | 4 | authentication_provider = authentication_provider 5 | secret_key = secret_key 6 | password_group = password_group 7 | management_user = management_user 8 | object_user = object_user 9 | -------------------------------------------------------------------------------- /ecsclient/v4/user_management/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.user_management import authentication_provider, secret_key, \ 2 | password_group, management_user, object_user 3 | 4 | authentication_provider = authentication_provider 5 | secret_key = secret_key 6 | password_group = password_group 7 | management_user = management_user 8 | object_user = object_user 9 | -------------------------------------------------------------------------------- /ecsclient/v2/provisioning/__init__.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.provisioning import base_url, bucket, data_store, storage_pool, virtual_data_center, node, \ 2 | vdc_keystore 3 | 4 | base_url = base_url 5 | bucket = bucket 6 | data_store = data_store 7 | storage_pool = storage_pool 8 | virtual_data_center = virtual_data_center 9 | node = node 10 | vdc_keystore = vdc_keystore 11 | -------------------------------------------------------------------------------- /tests/functional/test_license.py: -------------------------------------------------------------------------------- 1 | from ecsclient import schemas 2 | from tests import functional 3 | 4 | 5 | class TestLicense(functional.BaseTestCase): 6 | def test_get_license(self): 7 | response = self.client.licensing.get_license() 8 | self.assertValidSchema(response, schemas.LICENSE) 9 | 10 | def test_add_license(self): 11 | response = self.client.licensing.add_license(self.license_text) 12 | print(response) 13 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,34,35,36} 3 | 4 | [testenv] 5 | passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH 6 | deps = -r{toxinidir}/requirements.txt 7 | -r{toxinidir}/test-requirements.txt 8 | coveralls 9 | 10 | commands = flake8 ecsclient 11 | nosetests {posargs:--with-coverage --cover-tests --cover-package=ecsclient} 12 | - coveralls 13 | 14 | [flake8] 15 | exclude = .git,.idea,.tox,dist 16 | max-line-length = 120 17 | -------------------------------------------------------------------------------- /tests/functional/test_alerts.py: -------------------------------------------------------------------------------- 1 | from ecsclient import schemas 2 | from tests import functional 3 | 4 | 5 | class TestAlerts(functional.BaseTestCase): 6 | def test_alerts_no_params(self): 7 | response = self.client.alerts.get_alerts() 8 | # TODO: complete the schema with alert element validation 9 | self.assertValidSchema(response, schemas.ALERTS) 10 | 11 | def test_alerts_with_params(self): 12 | self.skipTest("Need to test it with params") 13 | -------------------------------------------------------------------------------- /ecsclient/v3/provisioning/node.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ecsclient.common.provisioning.node import Node 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | class Node(Node): 9 | 10 | def get_vdc_lock_status(self): 11 | raise NotImplementedError() 12 | 13 | def set_vdc_lock_status(self): 14 | raise NotImplementedError() 15 | 16 | def get_node_lock_status(self): 17 | raise NotImplementedError() 18 | 19 | def set_node_lock_status(self): 20 | raise NotImplementedError() 21 | -------------------------------------------------------------------------------- /ecsclient/v4/provisioning/node.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ecsclient.common.provisioning.node import Node 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | class Node(Node): 9 | 10 | def get_vdc_lock_status(self): 11 | raise NotImplementedError() 12 | 13 | def set_vdc_lock_status(self): 14 | raise NotImplementedError() 15 | 16 | def get_node_lock_status(self): 17 | raise NotImplementedError() 18 | 19 | def set_node_lock_status(self): 20 | raise NotImplementedError() 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, EMC Corporation 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /tests/functional/test_capacity.py: -------------------------------------------------------------------------------- 1 | from ecsclient import schemas 2 | from tests import functional 3 | 4 | 5 | class TestCapacity(functional.BaseTestCase): 6 | 7 | def test_get_cluster_capacity(self): 8 | response = self.client.capacity.get_cluster_capacity() 9 | self.assertValidSchema(response, schemas.CAPACITY) 10 | 11 | def test_get_cluster_capacity_storage_pool(self): 12 | sps = self.client.storage_pool.list() 13 | response = self.client.capacity.get_cluster_capacity(storage_pool_id=sps['varray'][0]['id']) 14 | self.assertValidSchema(response, schemas.CAPACITY) 15 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. python-ecsclient documentation master file, created by 2 | sphinx-quickstart on Mon Feb 20 14:58:42 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to python-ecsclient's documentation! 7 | ============================================ 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | 22 | 23 | This documentation site is still in development. Please come back soon. -------------------------------------------------------------------------------- /ecsclient/v3/cas/cas.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.cas import cas 2 | import logging 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | class Cas(cas.Cas): 8 | 9 | def update_cluster_id(self, cluster_id): 10 | """ 11 | Updates the cluster ID 12 | 13 | Required role(s): 14 | 15 | SYSTEM_ADMIN 16 | NAMESPACE_ADMIN 17 | 18 | There is no response body for this call 19 | 20 | Expect: HTTP/1.1 200 OK 21 | 22 | :param cluster_id: given cluster ID 23 | """ 24 | log.info("Updating cluster ID '{}'".format(cluster_id)) 25 | return self.conn.put('object/user-cas/cluster/{}'.format(cluster_id)) 26 | -------------------------------------------------------------------------------- /ecsclient/v4/cas/cas.py: -------------------------------------------------------------------------------- 1 | from ecsclient.common.cas import cas 2 | import logging 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | class Cas(cas.Cas): 8 | 9 | def update_cluster_id(self, cluster_id): 10 | """ 11 | Updates the cluster ID 12 | 13 | Required role(s): 14 | 15 | SYSTEM_ADMIN 16 | NAMESPACE_ADMIN 17 | 18 | There is no response body for this call 19 | 20 | Expect: HTTP/1.1 200 OK 21 | 22 | :param cluster_id: given cluster ID 23 | """ 24 | log.info("Updating cluster ID '{}'".format(cluster_id)) 25 | return self.conn.put('object/user-cas/cluster/{}'.format(cluster_id)) 26 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = python-ecsclient 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /ecsclient/common/configuration/feature.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Feature(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get_sse(self): 14 | """ 15 | Returns the feed for the details of ServerSideEncryption feature 16 | 17 | Required role(s): 18 | 19 | SYSTEM_ADMIN 20 | SYSTEM_MONITOR 21 | NAMESPACE_ADMIN 22 | 23 | Example JSON result from the API: 24 | {} 25 | """ 26 | # TODO: Add example JSON response 27 | log.info("Getting Server Side Encryption details") 28 | return self.conn.get('feature/ServerSideEncryption') 29 | -------------------------------------------------------------------------------- /ecsclient/common/other/user_info.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class UserInfo(object): 15 | 16 | def __init__(self, connection): 17 | """ 18 | Initialize a new instance 19 | """ 20 | self.conn = connection 21 | 22 | def whoami(self): 23 | """ 24 | Example JSON result from the API: 25 | 26 | { 27 | u'common_name': u'ecsadmin@internal', 28 | u'distinguished_name': u'', 29 | u'namespace': u'', 30 | u'roles': [ 31 | u'SYSTEM_ADMIN' 32 | ] 33 | } 34 | """ 35 | log.info('Getting my own user info (whoami)') 36 | return self.conn.get('user/whoami') 37 | -------------------------------------------------------------------------------- /tests/functional/test_vdc_keystore.py: -------------------------------------------------------------------------------- 1 | from ecsclient import schemas 2 | from tests import functional 3 | from tests.functional import helper 4 | 5 | 6 | class TestVdcKeystore(functional.BaseTestCase): 7 | 8 | def test_vdc_keystore_get(self): 9 | response = self.client.vdc_keystore.get() 10 | self.assertValidSchema(response, schemas.VDC_KEYSTORE) 11 | 12 | def test_vdc_keystore_set(self): 13 | private_key = helper.get_sample_private_key() 14 | certificate = helper.get_sample_certificate() 15 | response = self.client.vdc_keystore.set(private_key, certificate) 16 | self.assertValidSchema(response, schemas.VDC_KEYSTORE) 17 | # Can't validate the certificate and private key at this point 18 | # because it does not take effect until the ECS is manually restarted 19 | # self.assertSameCertificate(certificate, response['chain']) 20 | -------------------------------------------------------------------------------- /tests/unit/test_common.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ecsclient.common.util import get_formatted_time_string 3 | 4 | 5 | class TestCommonFunctions(unittest.TestCase): 6 | 7 | def setUp(self): 8 | self.time_bucket_no_minute = '2014-11-18T00' 9 | self.time_bucket_with_minute = '2014-11-18T00:01' 10 | 11 | def test_should_get_properly_formatted_timestamp_no_minute(self): 12 | self.assertEqual(self.time_bucket_no_minute, 13 | get_formatted_time_string(2014, 11, 18, 0, None)) 14 | 15 | def test_should_get_properly_formatted_timestamp_with_minute(self): 16 | self.assertEqual(self.time_bucket_with_minute, 17 | get_formatted_time_string(2014, 11, 18, 0, 1)) 18 | 19 | def test_should_throw_value_error(self): 20 | self.assertRaises(ValueError, 21 | get_formatted_time_string, 2014, 11, 18, 'abc') 22 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=python-ecsclient 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - '2.7' 5 | - '3.4' 6 | - '3.5' 7 | - '3.6' 8 | install: 9 | - pip install tox-travis 10 | script: tox 11 | deploy: 12 | - provider: pypi 13 | distributions: sdist 14 | user: __token__ 15 | password: 16 | secure: "a/waYjABu3Q/7WrXbJkVZiHcvJMerBTXQun73RxCQFiDcPe+zLQ1ZVY+VdvjWD7jDFYhp/DWBd4hvfe6/UJVRu4BMfZFap1DhHYCnaQxNeguClGTN4V/+o+bwvWIc4lRkqOmPW/cgXHEeAL6aEk7xtuQpnh54JgAnw3FRLa9HCAt0VKqcgi8z9+Rhmfa4CxxJyTYsY7OSTMVnniTAvbqoTCSvBq5nTYveg81lp4kHwUzfZBxgXKMRPuC0d5Yg2N8APXwFgYCLtF5vhR6pqaDeDp+KUvNVFaGYkNaRkaxJ8geBMC6OlyOk4Vt4aonVH362xDSS+R5cRA4EsovwJpoo4eO/QFCSaBtyiRahN2kdJ3Lb9KuM9ERmV/WeHX7cHbQH0UjaCGPlu7CKoRe5UAb2Kcq6HadFwNQd3IsSwyDNC2t6yor7fqwnvgAF22fM3I3jhPPMo6CY7P4KEEkICKOZVibh4+PudhlvI7K1gltFIQ7GTcD/a2HR2SagTOPXTjFgldyScHjY82PIjkFgHfwp1grz221n9tnKB24Vlys0WtAkpTxnrQj3JNrg45UtR0cAuBRUZmT7xDJXB25CIPY22Co/QSGYSuU/IzrmfL/EwFu/2dRw8kMqFUROhZGwk1AsR89vfSQhNRRw9APAFANyRvnQFROETP7KJY7nbU2vMk=" 17 | on: 18 | tags: true 19 | python: 3.6 20 | skip_cleanup: true 21 | -------------------------------------------------------------------------------- /ecsclient/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import ecsclient.v2.client as v2_client 4 | import ecsclient.v3.client as v3_client 5 | import ecsclient.v4.client as v4_client 6 | 7 | _logger = logging.getLogger(__name__) 8 | 9 | _CLIENT_VERSIONS = {'2': v2_client.Client, 10 | '3': v3_client.Client, 11 | '4': v4_client.Client} 12 | 13 | 14 | def Client(version=None, *args, **kwargs): 15 | """Factory function to create a new ECS client. 16 | 17 | The returned client will be either a V3 or V2 client. Check the version 18 | using the :py:attr:`~ecsclient.v2.client.Client.version` property or 19 | the instance's class (with instanceof). 20 | 21 | :param string version: The required version of the ECS Management API. 22 | """ 23 | 24 | if not version: 25 | msg = "Please provide the API version. Options are: '2', '3'." 26 | raise RuntimeError(msg) 27 | 28 | try: 29 | client_class = _CLIENT_VERSIONS[version] 30 | except KeyError: 31 | msg = "No client available for version '%s'" % version 32 | raise RuntimeError(msg) 33 | 34 | return client_class(*args, **kwargs) 35 | -------------------------------------------------------------------------------- /tests/functional/test_certificate.py: -------------------------------------------------------------------------------- 1 | from ecsclient import schemas 2 | from tests import functional 3 | from tests.functional import helper 4 | 5 | 6 | class TestCertificate(functional.BaseTestCase): 7 | def test_get_certificate(self): 8 | response = self.client.certificate.get_certificate_chain() 9 | self.assertValidSchema(response, schemas.CERTIFICATE) 10 | 11 | def test_set_certificate(self): 12 | # First, set a self-signed certificate 13 | ip_addresses = ['10.0.0.1'] 14 | response = self.client.certificate.set_certificate_chain( 15 | selfsigned=True, 16 | ip_addresses=ip_addresses) 17 | self.assertValidSchema(response, schemas.CERTIFICATE) 18 | 19 | # Then, provide a private key and a certificate 20 | private_key = helper.get_sample_private_key() 21 | certificate = helper.get_sample_certificate() 22 | response = self.client.certificate.set_certificate_chain( 23 | private_key=private_key, 24 | certificate_chain=certificate) 25 | self.assertValidSchema(response, schemas.CERTIFICATE) 26 | self.assertSameCertificate(certificate, response['chain']) 27 | -------------------------------------------------------------------------------- /ecsclient/common/monitoring/capacity.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Capacity(object): 7 | 8 | def __init__(self, connection): 9 | """ 10 | Initialize a new instance 11 | """ 12 | self.conn = connection 13 | 14 | def get_cluster_capacity(self, storage_pool_id=None): 15 | """ 16 | Gets the capacity of the cluster. The details includes the provisioned 17 | capacity in GB and available capacity in GB. 18 | 19 | Required role(s): 20 | 21 | SYSTEM_ADMIN 22 | SYSTEM_MONITOR 23 | 24 | Example JSON result from the API: 25 | 26 | { 27 | u'totalFree_gb': 1272085, 28 | u'totalProvisioned_gb': 2578400 29 | } 30 | 31 | :param storage_pool_id: Storage pool identifier for which to retrieve 32 | capacity (optional) 33 | """ 34 | if storage_pool_id: 35 | log.info("Getting capacity of storage pool '{0}'".format(storage_pool_id)) 36 | return self.conn.get(url='object/capacity/{0}'.format(storage_pool_id)) 37 | else: 38 | log.info("Getting capacity of storage cluster") 39 | return self.conn.get(url='object/capacity') 40 | -------------------------------------------------------------------------------- /ecsclient/authentication.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Authentication(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def logout(self, force=False): 14 | """ 15 | Perform an HTTP GET against the ECS endpoint to 16 | log an authenticated user out, thereby invalidating their 17 | authentication token. 18 | 19 | Example JSON result from the API: 20 | 21 | {"user": "root"} 22 | 23 | :param force: If you have multiple sessions running simultaneously this 24 | forces the termination of all tokens to the current user 25 | """ 26 | 27 | if not self.conn.get_current_token(): 28 | log.warning('Not logging out since the client has no token set up') 29 | return 30 | 31 | params = { 32 | 'force': force 33 | } 34 | 35 | log.info('Terminating session (signing out): {0}'.format(params)) 36 | 37 | if force: 38 | logout_resp = self.conn.get('logout', params=params) 39 | else: 40 | logout_resp = self.conn.get('logout') 41 | 42 | # Remove cached authorization token from disk, as the session is 43 | # now terminated. 44 | self.conn.remove_cached_token() 45 | 46 | return logout_resp 47 | -------------------------------------------------------------------------------- /ecsclient/common/provisioning/node.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class Node(object): 15 | 16 | def __init__(self, connection): 17 | """ 18 | Initialize a new instance 19 | """ 20 | self.conn = connection 21 | 22 | def list(self): 23 | """ 24 | Gets a list of the data nodes that are currently configured in the cluster. 25 | 26 | Required role(s): 27 | 28 | SYSTEM_ADMIN 29 | SYSTEM_MONITOR 30 | NAMESPACE_ADMIN 31 | 32 | Example JSON result from the API: 33 | 34 | { 35 | u'node': [ 36 | { 37 | u'ip': u'172.29.3.148', 38 | u'version': u'1.2.0.0.60071.ffbe16c', 39 | u'rackId': u'gray', 40 | u'nodename': u'supr01-r01-01.lax01s1.rspaas-lab.ops.com', 41 | u'nodeid': u'171.29.3.140' 42 | }, 43 | { 44 | u'ip': u'172.29.3.149', 45 | u'version': u'1.2.0.0.60071.ffbe16c', 46 | u'rackId': u'gray', 47 | u'nodename': u'supr01-r01-02.lax01s1.rspaas-lab.ops.com', 48 | u'nodeid': u'171.29.3.141' 49 | } 50 | ] 51 | } 52 | """ 53 | log.info('Getting all nodes for cluster') 54 | return self.conn.get(url='vdc/nodes') 55 | -------------------------------------------------------------------------------- /ecsclient/common/util.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | from jsonschema import validate, FormatChecker 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | def get_formatted_time_string(year, month, day, hour, minute=None): 9 | """ 10 | Validates input parameters: year, month, day, hour and minute 11 | and returns the timestamp if valid 12 | 13 | :param year: Four digit year 14 | :param month: The month 15 | :param day: The day 16 | :param hour: The hour 17 | :param minute: The minutes (optional) 18 | :return: Returns time stamp in yyyy-MM-dd'T'HH:mm if parameters are valid 19 | 20 | Throws: 21 | ValueError in case of invalid input 22 | """ 23 | 24 | if minute: 25 | d = datetime.datetime(int(year), int(month), int(day), int(hour), 26 | int(minute)) 27 | return d.strftime("%Y-%m-%dT%H:%M") 28 | else: 29 | d = datetime.datetime(int(year), int(month), int(day), int(hour)) 30 | return d.strftime("%Y-%m-%dT%H") 31 | 32 | 33 | def is_valid_response(response, schema): 34 | """ 35 | Returns True if the response validates with the schema, False otherwise 36 | 37 | :param response: The response returned by the API 38 | :param schema: The schema to validate with 39 | :returns: True if the response validates with the schema, False otherwise 40 | """ 41 | try: 42 | validate(response, schema, format_checker=FormatChecker()) 43 | return True 44 | except Exception as e: 45 | log.warning("Response is not valid: %s" % (e,)) 46 | return False 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | try: 3 | from setuptools import setup, find_packages 4 | except ImportError: 5 | from ez_setup import use_setuptools 6 | 7 | use_setuptools() 8 | from setuptools import setup, find_packages 9 | 10 | 11 | def read(relative): 12 | """ 13 | Read file contents and return a list of lines. 14 | ie, read the VERSION file 15 | """ 16 | contents = open(relative, 'r').read() 17 | return [l for l in contents.split('\n') if l != ''] 18 | 19 | 20 | with open('README.rst', 'r') as f: 21 | readme = f.read() 22 | 23 | setup( 24 | name='python-ecsclient', 25 | url='https://github.com/EMCECS/python-ecsclient', 26 | keywords=['ecsclient'], 27 | version=read('VERSION')[0], 28 | description='A library for interacting with the ECS Management API', 29 | author='ECS', 30 | author_email='ecs@dell.com', 31 | tests_require=read('./test-requirements.txt'), 32 | install_requires=read('./requirements.txt'), 33 | test_suite='nose.collector', 34 | zip_safe=False, 35 | include_package_data=True, 36 | packages=find_packages(exclude=['ez_setup']), 37 | classifiers=[ 38 | 'Development Status :: 5 - Production/Stable', 39 | 'Intended Audience :: Developers', 40 | 'License :: OSI Approved :: Apache Software License', 41 | 'Operating System :: OS Independent', 42 | 'Programming Language :: Python', 43 | 'Programming Language :: Python :: 2', 44 | 'Programming Language :: Python :: 2.7', 45 | 'Programming Language :: Python :: 3', 46 | 'Programming Language :: Python :: 3.4', 47 | 'Programming Language :: Python :: 3.5', 48 | 'Programming Language :: Python :: 3.6', 49 | 'Topic :: Software Development :: Libraries :: Python Modules', 50 | ], 51 | ) 52 | -------------------------------------------------------------------------------- /ecsclient/common/provisioning/vdc_keystore.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class VdcKeystore(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get(self): 14 | """ 15 | Get the certificate chain being used by ECS. 16 | 17 | Required role(s): 18 | 19 | This call has no restrictions. 20 | 21 | Example JSON result from the API: 22 | 23 | { 24 | "chain": "-----BEGIN CERTIFICATE-----\nMIIDBjCCAe4..." 25 | } 26 | """ 27 | log.info("Getting the certificate chain") 28 | return self.conn.get('vdc/keystore') 29 | 30 | def set(self, private_key, certificate_chain): 31 | """ 32 | Set the private key and certificate chain being used by ECS. 33 | WARNING: Note that the certificate will not be updated until ECS is 34 | restarted. Restarting ECS is out of the scope of this library. 35 | 36 | Required role(s): 37 | 38 | SYSTEM_ADMIN 39 | 40 | Example JSON result from the API: 41 | 42 | { 43 | "chain": "-----BEGIN CERTIFICATE-----\nMIIDBjCCAe4..." 44 | } 45 | 46 | :param private_key: The private key to be set 47 | :param certificate_chain: The certificate chain to be set 48 | """ 49 | payload = { 50 | "key_and_certificate": { 51 | "private_key": private_key, 52 | "certificate_chain": certificate_chain 53 | } 54 | } 55 | 56 | log.info("Setting the private key and certificate chain (ECS must be " 57 | "restarted for this change to take effect)") 58 | return self.conn.put('vdc/keystore', json_payload=payload) 59 | -------------------------------------------------------------------------------- /ecsclient/common/monitoring/alerts.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Alerts(object): 7 | 8 | def __init__(self, connection): 9 | """ 10 | Initialize a new instance 11 | """ 12 | self.conn = connection 13 | 14 | def get_alerts(self, namespace=None, start_time=None, end_time=None, marker=None, limit=None, 15 | severity=None, type=None, acknowledged=None): 16 | """ 17 | Gets the list of alerts with optional filters 18 | 19 | Required role(s): 20 | 21 | SYSTEM_ADMIN 22 | 23 | Example JSON result from the API: 24 | 25 | { 26 | ... 27 | } 28 | 29 | :param namespace: Namespace for which alerts should be listed 30 | :param start_time: Start time for listing alerts 31 | :param end_time: End time for listing alerts 32 | :param marker: Reference to last alert returned 33 | :param limit: Number of alerts requested in current fetch 34 | :param severity: Severity of alerts to be listed 35 | :param type: Type of alerts to be listed 36 | :param acknowledged: Boolean to filter by acknowledgement 37 | """ 38 | 39 | filters = {} 40 | if namespace: 41 | filters['namespace'] = namespace 42 | if start_time: 43 | filters['start_time'] = start_time 44 | if end_time: 45 | filters['end_time'] = end_time 46 | if marker: 47 | filters['marker'] = marker 48 | if limit: 49 | filters['limit'] = limit 50 | if severity: 51 | filters['severity'] = severity 52 | if type: 53 | filters['type'] = type 54 | if acknowledged: 55 | filters['acknowledged'] = acknowledged 56 | 57 | log.info("Getting alerts with filters: %s", (filters,)) 58 | return self.conn.get(url='vdc/alerts', params=filters) 59 | -------------------------------------------------------------------------------- /tests/functional/test_tenant.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ecsclient import schemas 3 | from ecsclient.common.exceptions import ECSClientException 4 | from tests import functional 5 | from six.moves import configparser 6 | 7 | 8 | class TestTenant(functional.BaseTestCase): 9 | """ 10 | Test conf sample 11 | [func_test] 12 | token_endpoint = https://10.0.1.1:4443/login 13 | ecs_endpoint = https://10.0.1.1:4443 14 | username = root 15 | password = k912oz2chpsy8tny 16 | api_version = 3 17 | license_file = /home/user/license.lic 18 | override_header = true 19 | account_id = 6bd95656-42df-4e9e-9b19-b05a660eca81 20 | """ 21 | def __init__(self, *args, **kwargs): 22 | super(TestTenant, self).__init__(*args, **kwargs) 23 | config_file = os.environ.get('ECS_TEST_CONFIG_FILE', 24 | os.path.join(os.getcwd(), "tests/test.conf")) 25 | config = configparser.ConfigParser() 26 | config.read(config_file) 27 | self.config = config 28 | if config.has_section('func_test'): 29 | self.tenant = config.get('func_test', 'account_id') 30 | else: 31 | self.skip_tests = True 32 | 33 | def setUp(self): 34 | super(TestTenant, self).setUp() 35 | if self.skip_tests: 36 | self.skipTest('SKIPPING FUNCTIONAL TESTS DUE TO NO CONFIG') 37 | self.client.tenant.create(self.tenant) 38 | 39 | def tearDown(self): 40 | super(TestTenant, self).tearDown() 41 | try: 42 | self.client.tenant.delete(self.tenant) 43 | except ECSClientException: 44 | pass 45 | 46 | def test_tenants_list(self): 47 | response = self.client.tenant.list() 48 | self.assertValidSchema(response, schemas.TENANTS) 49 | 50 | def test_tenants_get_one(self): 51 | response = self.client.tenant.get(self.tenant) 52 | self.assertValidSchema(response, schemas.TENANT) 53 | 54 | def test_tenants_delete(self): 55 | self.client.tenant.delete(self.tenant) 56 | f = self.client.tenant.get 57 | self.assertRaises(ECSClientException, f, self.tenant) 58 | -------------------------------------------------------------------------------- /tests/functional/test_vdc.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from tests import functional 5 | 6 | 7 | class TestVDC(functional.BaseTestCase): 8 | def setUp(self): 9 | super(TestVDC, self).setUp() 10 | r = self.client.vdc.get_local() 11 | self.vdc_1_id = r['id'] 12 | self.vdc_1_name = r['name'] 13 | self.vdc_1_endpoint = r['interVdcEndPoints'] 14 | 15 | def test_vdc_list(self): 16 | response = self.client.vdc.list() 17 | self.assertValidSchema(response, schemas.VDCS) 18 | 19 | def test_vdc_get_by_id(self): 20 | response = self.client.vdc.get(vdc_id=self.vdc_1_id) 21 | self.assertValidSchema(response, schemas.VDC) 22 | self.assertEqual(response['id'], self.vdc_1_id) 23 | self.assertEqual(response['vdcId'], self.vdc_1_id) 24 | 25 | def test_vdc_get_by_name(self): 26 | response = self.client.vdc.get(name=self.vdc_1_name) 27 | self.assertValidSchema(response, schemas.VDC) 28 | self.assertEqual(response['name'], self.vdc_1_name) 29 | self.assertEqual(response['vdcName'], self.vdc_1_name) 30 | 31 | def test_vdc_get_local(self): 32 | response = self.client.vdc.get_local() 33 | self.assertValidSchema(response, schemas.VDC) 34 | self.assertTrue(response['local']) 35 | 36 | def test_vdc_get_local_secret_key(self): 37 | response = self.client.vdc.get_local_secret_key() 38 | self.assertIn('key', response) 39 | self.assertIsNotNone(response['key']) 40 | 41 | def test_vdc_update(self): 42 | vdc_name = 'vdc-%d' % time.time() 43 | secret_key = '%d' % time.time() 44 | self.client.vdc.update(self.vdc_1_name, 45 | new_name=vdc_name, 46 | secret_key=secret_key, 47 | inter_vdc_endpoints=self.vdc_1_endpoint, 48 | inter_vdc_cmd_endpoints=self.vdc_1_endpoint, 49 | management_endpoints=self.vdc_1_endpoint 50 | ) 51 | 52 | r = self.client.vdc.get(vdc_id=self.vdc_1_id) 53 | self.assertEqual(r['name'], vdc_name) 54 | self.assertEqual(r['vdcName'], vdc_name) 55 | self.assertEqual(r['secretKeys'], secret_key) 56 | 57 | def test_vdc_delete(self): 58 | self.skipTest('Cannot create a scenario to delete a VDC') 59 | -------------------------------------------------------------------------------- /tests/functional/test_namespace.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from ecsclient.common.exceptions import ECSClientException 5 | from tests import functional 6 | 7 | 8 | class TestNamespace(functional.BaseTestCase): 9 | def __init__(self, *args, **kwargs): 10 | super(TestNamespace, self).__init__(*args, **kwargs) 11 | self.namespace_1 = "functional-tests-namespace-%s" % int(time.time()) 12 | self.namespace_2 = self.namespace_1 + '_second' 13 | self.namespace_3 = self.namespace_1 + '_third' 14 | 15 | def setUp(self): 16 | super(TestNamespace, self).setUp() 17 | self.client.namespace.create(self.namespace_1) 18 | self.client.namespace.create(self.namespace_3) 19 | 20 | def tearDown(self): 21 | super(TestNamespace, self).tearDown() 22 | for namespace in [self.namespace_1, 23 | self.namespace_2, 24 | self.namespace_3]: 25 | try: 26 | self.client.namespace.delete(namespace) 27 | except ECSClientException: 28 | pass 29 | 30 | def test_namespaces_list(self): 31 | response = self.client.namespace.list() 32 | self.assertValidSchema(response, schemas.NAMESPACES) 33 | 34 | def test_namespaces_get_one(self): 35 | response = self.client.namespace.get(self.namespace_1) 36 | self.assertValidSchema(response, schemas.NAMESPACE) 37 | 38 | def test_namespaces_update(self): 39 | # Get the namespace and verify the value of one of its attributes 40 | response = self.client.namespace.get(self.namespace_1) 41 | self.assertEqual(response['is_stale_allowed'], False) 42 | 43 | # Update the attribute 44 | self.client.namespace.update(self.namespace_1, is_stale_allowed=True) 45 | 46 | # Get it again and verify that the value was correctly updated 47 | response = self.client.namespace.get(self.namespace_1) 48 | self.assertEqual(response['is_stale_allowed'], True) 49 | 50 | def test_namespaces_create(self): 51 | response = self.client.namespace.create(self.namespace_2) 52 | self.assertValidSchema(response, schemas.NAMESPACE) 53 | self.assertEqual(response['name'], self.namespace_2) 54 | 55 | def test_namespaces_delete(self): 56 | self.client.namespace.delete(self.namespace_3) 57 | f = self.client.namespace.get 58 | self.assertRaises(ECSClientException, f, self.namespace_3) 59 | -------------------------------------------------------------------------------- /tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import unittest 4 | 5 | from six.moves import configparser 6 | 7 | from ecsclient.client import Client 8 | from ecsclient.common.util import is_valid_response 9 | 10 | 11 | class BaseTestCase(unittest.TestCase): 12 | def __init__(self, *args, **kwargs): 13 | super(BaseTestCase, self).__init__(*args, **kwargs) 14 | self.skip_tests = False 15 | self._get_config() 16 | 17 | def _get_config(self): 18 | config_file = os.environ.get('ECS_TEST_CONFIG_FILE', 19 | os.path.join(os.getcwd(), "tests/test.conf")) 20 | config = configparser.ConfigParser() 21 | config.read(config_file) 22 | self.config = config 23 | if config.has_section('func_test'): 24 | self.token_endpoint = config.get('func_test', 'token_endpoint') 25 | self.ecs_endpoint = config.get('func_test', 'ecs_endpoint') 26 | self.username = config.get('func_test', 'username') 27 | self.password = config.get('func_test', 'password') 28 | self.api_version = config.get('func_test', 'api_version') 29 | license_file = config.get('func_test', 'license_file') 30 | with open(license_file) as f: 31 | self.license_text = f.read() 32 | self.override_header = config.get("func_test", 'override_header') 33 | else: 34 | self.skip_tests = True 35 | 36 | def _get_client(self): 37 | return Client( 38 | self.api_version, 39 | username=self.username, 40 | password=self.password, 41 | ecs_endpoint=self.ecs_endpoint, 42 | token_endpoint=self.token_endpoint, 43 | override_header=self.override_header) 44 | 45 | def setUp(self): 46 | super(BaseTestCase, self).setUp() 47 | if self.skip_tests: 48 | self.skipTest('SKIPPING FUNCTIONAL TESTS DUE TO NO CONFIG') 49 | self.client = self._get_client() 50 | 51 | def tearDown(self): 52 | super(BaseTestCase, self).tearDown() 53 | 54 | def assertSameCertificate(self, first, second, msg=None): 55 | """Fail if the two certificates are not equal. 56 | """ 57 | first = re.sub(r"\s+", "", first) 58 | second = re.sub(r"\s+", "", second) 59 | self.assertEqual(first, second, msg=msg) 60 | 61 | def assertValidSchema(self, object, schema): 62 | """Fail if the object does not comply with the given schema. 63 | """ 64 | self.assertTrue(is_valid_response(object, schema), 'Response is not valid with the schema') 65 | -------------------------------------------------------------------------------- /ecsclient/common/configuration/certificate.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Certificate(object): 7 | 8 | def __init__(self, connection): 9 | """ 10 | Initialize a new instance 11 | """ 12 | self.conn = connection 13 | 14 | def get_certificate_chain(self): 15 | """ 16 | Gets the certificate chain currently used. The data returned will 17 | contain all of the certificates in the current certificate chain 18 | encoded in PEM format. 19 | 20 | Required role(s): 21 | 22 | SYSTEM_ADMIN 23 | 24 | Example JSON result from the API: 25 | 26 | { 27 | u'chain': u'-----BEGINCERTIFICATE-----\nMIIEnTCCA4WgAwI- 28 | REALLY-LONG-CERT\r\n-----ENDCERTIFICATE-----' 29 | } 30 | """ 31 | log.info("Fetching certificate chain") 32 | return self.conn.get('object-cert/keystore') 33 | 34 | def set_certificate_chain(self, selfsigned=False, ip_addresses=None, private_key=None, certificate_chain=None): 35 | """ 36 | Sets private key and certificate pair. The new certificate and key 37 | will be rotated into all of the nodes within 1 hour. 38 | 39 | Required role(s): 40 | 41 | SYSTEM_ADMIN 42 | 43 | Example JSON result from the API 44 | 45 | { 46 | 'chain': '-----BEGINCERTIFICATE-----\nMIIEnTCCA4WgAwI- 47 | REALLY-LONG-CERT\r\n-----ENDCERTIFICATE-----' 48 | } 49 | 50 | :param selfsigned: Set true if the you want the system to generate a new self-signed certificate, 51 | false otherwise 52 | :param ip_addresses: List of IP addresses. The IP addresses are taken into account only if selfsigned 53 | is set to true. i.e, User wants the system to generate a new self-signed certificate 54 | :param private_key: The private key used to sign the certificate in PEM format 55 | :param certificate_chain: New certificate for the nodes in PEM format. For certificates signed by an 56 | intermediate CA (most are), the intermediate certificate(s) should be concatenated to the text string, 57 | also in PEM format. 58 | """ 59 | payload = { 60 | "system_selfsigned": selfsigned 61 | } 62 | if selfsigned: 63 | payload["ip_addresses"] = ip_addresses 64 | else: 65 | payload["key_and_certificate"] = { 66 | "private_key": private_key, 67 | "certificate_chain": certificate_chain 68 | } 69 | log.info("Setting certificate chain") 70 | return self.conn.put('object-cert/keystore', json_payload=payload) 71 | -------------------------------------------------------------------------------- /ecsclient/common/geo_replication/temporary_failed_zone.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class TemporaryFailedZone(object): 15 | 16 | def __init__(self, connection): 17 | """ 18 | Initialize a new instance 19 | """ 20 | self.conn = connection 21 | 22 | def get_all_temp_failed_zones(self): 23 | """ 24 | Gets all the configured temp failed zones. 25 | 26 | Required role(s): 27 | 28 | SYSTEM_ADMIN 29 | SYSTEM_MONITOR 30 | 31 | Example JSON result from the API: 32 | 33 | { 34 | u'tempfailedzone': [ 35 | { 36 | u'remote': None, 37 | u'tags': [], 38 | u'global': None, 39 | u'rgId': u'urn: storageos: ReplicationGroupInfo: 40 | c2b0d3c4-c778-4a24-8da5-6a89784c4eeb: global', 41 | u'vdc': None, 42 | u'inactive': False, 43 | u'failedZoneList': [] 44 | }, 45 | { 46 | u'remote': None, 47 | u'tags': [], 48 | u'global': None, 49 | u'rgId': u'urn: storageos: ReplicationGroupInfo: 50 | 4ea1fa1e-a7d1-4a8e-b8cc-e5a2c27f308d: global', 51 | u'vdc': None, 52 | u'inactive': False, 53 | u'failedZoneList': [] 54 | } 55 | ] 56 | } 57 | """ 58 | log.info("Fetching all Temporary Failed Zones") 59 | return self.conn.get(url='tempfailedzone/allfailedzones') 60 | 61 | def get_temp_failed_zone(self, replication_group_id): 62 | """ 63 | Gets all the temp failed zones for the specified replication group 64 | identifier. 65 | 66 | Required role(s): 67 | 68 | SYSTEM_ADMIN 69 | SYSTEM_MONITOR 70 | 71 | Example JSON result from the API: 72 | 73 | { 74 | u'remote': None, 75 | u'tags': [], 76 | u'global': None, 77 | u'rgId': u'urn: storageos: ReplicationGroupInfo: 78 | c2b0d3c4-c778-4a24-8da5-6a89784c4eeb: global', 79 | u'vdc': None, 80 | u'inactive': False, 81 | u'failedZoneList': [] 82 | } 83 | 84 | :param replication_group_id: Replication group id to retrieve details 85 | """ 86 | log.info("Fetching TFZs for vpool '{0}'".format(replication_group_id)) 87 | 88 | return self.conn.get( 89 | url='tempfailedzone/rgid/{0}'.format(replication_group_id)) 90 | -------------------------------------------------------------------------------- /tests/functional/test_data_store.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from tests import functional 5 | 6 | 7 | class TestDataStore(functional.BaseTestCase): 8 | def setUp(self): 9 | super(TestDataStore, self).setUp() 10 | 11 | # Get a data store ID 12 | r = self.client.data_store.list() 13 | self.data_store_1_id = r['data_store'][0]['id'] 14 | 15 | # Get a storage pool ID 16 | r = self.client.data_store.get(self.data_store_1_id) 17 | self.storage_pool_1_id = r['varray'] 18 | 19 | # Create a new data store to get a task ID 20 | self.data_store_2_name = "functional-tests-datastore-%s" % int(time.time()) 21 | r = self.client.data_store.create(name=self.data_store_2_name, 22 | description="Data Store for functional test", 23 | node_id=self.data_store_1_id, 24 | storage_pool_id=self.storage_pool_1_id) 25 | self.task_1_id = r['task'][0]['op_id'] 26 | 27 | def test_data_store_list(self): 28 | response = self.client.data_store.list() 29 | self.assertValidSchema(response, schemas.DATA_STORES) 30 | 31 | def test_data_store_get(self): 32 | response = self.client.data_store.get(self.data_store_1_id) 33 | self.assertValidSchema(response, schemas.DATA_STORE) 34 | self.assertEqual(response['varray'], self.storage_pool_1_id) 35 | 36 | def test_data_store_get_by_storage_pool(self): 37 | response = self.client.data_store.get_by_storage_pool(self.storage_pool_1_id) 38 | self.assertValidSchema(response, schemas.DATA_STORES_COMMODITY) 39 | self.assertEqual(response['commodity_data_store'][0]['varray'], self.storage_pool_1_id) 40 | 41 | def test_data_store_create(self): 42 | response = self.client.data_store.create(name=self.data_store_2_name, 43 | description="Data Store for functional test", 44 | node_id=self.data_store_1_id, 45 | storage_pool_id=self.storage_pool_1_id) 46 | self.assertValidSchema(response, schemas.DATA_STORE_TASKS) 47 | 48 | def test_data_store_delete(self): 49 | # TODO: API is returning an error. Need to investigate 50 | self.skipTest('Skipping until investigated') 51 | # Try to delete a non-existent data store 52 | response = self.client.data_store.delete(data_store_id='9.9.9.9') 53 | self.assertValidSchema(response, schemas.DATA_STORE_TASKS) 54 | 55 | def test_data_store_get_task(self): 56 | response = self.client.data_store.get_task(data_store_id=self.data_store_1_id, op_id=self.task_1_id) 57 | self.assertValidSchema(response, schemas.DATA_STORE_TASK) 58 | self.assertEqual(response['op_id'], self.task_1_id) 59 | -------------------------------------------------------------------------------- /ecsclient/common/monitoring/events.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class Events(object): 15 | 16 | def __init__(self, connection): 17 | """ 18 | Initialize a new instance 19 | """ 20 | self.conn = connection 21 | 22 | def get_audit_events(self, start_time, end_time, namespace, limit=10, 23 | marker=None): 24 | """ 25 | Gets audit events for the specified namespace identifier and interval. 26 | 27 | Required role(s): 28 | 29 | SYSTEM_ADMIN 30 | SYSTEM_MONITOR 31 | 32 | Example JSON result from the API: 33 | 34 | { 35 | u'MaxEvents': 2, 36 | u'NextMarker': u'kNDVkLTRkYzYtOWUxNy03MGFkYzAzMWUxNDQ=', 37 | u'auditevent': [ 38 | { 39 | u'serviceType': u'Bucket', 40 | u'description': u'BucketCreated', 41 | u'eventType': u'Created', 42 | u'userId': u'user1', 43 | u'timestamp': u'2015-06-08T01: 30', 44 | u'namespace': u'namespace1', 45 | u'resourceId': u'namespace1.ecstest-2a925333-5f91-43d0-b1', 46 | u'id': u'urn: storageos: Event: 29ffd597-7c64-41ef-bc5c-e2' 47 | }, 48 | { 49 | u'serviceType': u'Bucket', 50 | u'description': u'BucketCreated', 51 | u'eventType': u'Created', 52 | u'userId': u'someone1', 53 | u'timestamp': u'2015-06-08T01: 49', 54 | u'namespace': u'namespace1', 55 | u'resourceId': u'namespace1.ecstest-18880408-64ed-4380-80', 56 | u'id': u'urn: storageos: Event: 1ee5c8c2-be67-478a-86ac' 57 | } 58 | ] 59 | } 60 | 61 | Time format is: yyyy-MM-dd'T'HH:mm 62 | Example: 2015-01-25T04:05 63 | 64 | :param start_time: Start time for the interval to retrieve audit events 65 | :param end_time: End time for the interval to retrieve audit events 66 | :param namespace: Namespace identifier for which audit events needs to 67 | be retrieved. 68 | :param limit: Number of audit events requested in current fetch. 69 | :param marker: Reference of last audit event returned 70 | """ 71 | log.info("Getting audit events for namespace '{0}'".format(namespace)) 72 | 73 | params = { 74 | 'start_time': start_time, 75 | 'end_time': end_time, 76 | 'namespace': namespace, 77 | 'limit': limit 78 | } 79 | 80 | if marker: 81 | params['marker'] = marker 82 | 83 | return self.conn.get(url='vdc/events', params=params) 84 | -------------------------------------------------------------------------------- /ecsclient/common/configuration/licensing.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class Licensing(object): 15 | def __init__(self, connection): 16 | """ 17 | Initialize a new instance 18 | """ 19 | self.conn = connection 20 | 21 | def get_license(self): 22 | """ 23 | Gets the currently configured licenses. 24 | 25 | Required role(s): 26 | 27 | SYSTEM_ADMIN 28 | SYSTEM_MONITOR 29 | 30 | Example JSON result from the API: 31 | 32 | { 33 | u'license_feature': [ 34 | { 35 | u'notice': u'ACTIVATEDTOLicenseSiteNumber: 36 | PTA06JUN20131086059', 37 | u'trial_license_ind': False, 38 | u'expired_ind': False, 39 | u'licensed_ind': True, 40 | u'site_id': u'UNKNOWN', 41 | u'product': u'PXTYD1DZK59Y4C', 42 | u'issued_date': u'01/10/2014', 43 | u'version': u'2.0', 44 | u'storage_capacity': u'TB', 45 | u'license_id_indicator': u'U', 46 | u'model': u'ViPR_Block', 47 | u'serial': u'PRTYD1DZK59X9A', 48 | u'issuer': u'EMC' 49 | }, 50 | { 51 | u'notice': u'ACTIVATEDTOLicenseSiteNumber: 52 | PTA06JUN20131086059', 53 | u'trial_license_ind': False, 54 | u'expired_ind': False, 55 | u'licensed_ind': True, 56 | u'site_id': u'UNKNOWN', 57 | u'product': u'PXTYD1DZK59Y4C', 58 | u'issued_date': u'01/10/2014', 59 | u'version': u'2.0', 60 | u'storage_capacity': u'TB', 61 | u'license_id_indicator': u'U', 62 | u'model': u'ViPR_Commodity', 63 | u'serial': u'PRTYD1DZK59X9A', 64 | u'issuer': u'EMC' 65 | } 66 | ], 67 | u'license_text': u'LONG-LONG--TEXT' 68 | } 69 | """ 70 | log.info("Retrieving license") 71 | return self.conn.get('license') 72 | 73 | def add_license(self, license_text): 74 | """ 75 | Adds specified license. 76 | 77 | Required role(s): 78 | 79 | SYSTEM_ADMIN 80 | 81 | There is no response body for this call 82 | 83 | Expect: HTTP/1.1 200 OK 84 | 85 | :param license_text: License text to be added 86 | """ 87 | payload = { 88 | "license_text": license_text 89 | } 90 | log.info("Adding a new license") 91 | return self.conn.post('license.json', json_payload=payload) 92 | -------------------------------------------------------------------------------- /tests/functional/test_storage_pool.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from ecsclient.common.exceptions import ECSClientException 5 | from tests import functional 6 | 7 | 8 | class TestStoragePool(functional.BaseTestCase): 9 | def __init__(self, *args, **kwargs): 10 | super(TestStoragePool, self).__init__(*args, **kwargs) 11 | self.storage_pool_1 = "functional-tests-storagepool-%s" % int(time.time()) 12 | self.storage_pool_2 = self.storage_pool_1 + '_second' 13 | self.storage_pool_3 = self.storage_pool_1 + '_third' 14 | 15 | def setUp(self): 16 | super(TestStoragePool, self).setUp() 17 | r = self.client.storage_pool.create(self.storage_pool_1) 18 | self.storage_pool_1_id = r['id'] 19 | self.storage_pool_2_id = 'placeholder' 20 | r = self.client.storage_pool.create(self.storage_pool_3) 21 | self.storage_pool_3_id = r['id'] 22 | 23 | def tearDown(self): 24 | super(TestStoragePool, self).tearDown() 25 | for storage_pool_id in [self.storage_pool_1_id, 26 | self.storage_pool_2_id, 27 | self.storage_pool_3_id]: 28 | try: 29 | self.client.storage_pool.delete(storage_pool_id) 30 | except ECSClientException: 31 | pass 32 | 33 | def test_storage_pools_list(self): 34 | response = self.client.storage_pool.list() 35 | self.assertValidSchema(response, schemas.STORAGE_POOLS) 36 | 37 | def test_storage_pools_get_one(self): 38 | response = self.client.storage_pool.get(self.storage_pool_1_id) 39 | self.assertValidSchema(response, schemas.STORAGE_POOL) 40 | 41 | def test_storage_pools_create(self): 42 | response = self.client.storage_pool.create(self.storage_pool_2) 43 | self.assertValidSchema(response, schemas.STORAGE_POOL) 44 | self.assertEqual(response['name'], self.storage_pool_2) 45 | 46 | def test_storage_pools_update(self): 47 | new_name = self.storage_pool_1 + '-updated' 48 | # Get the namespace and verify the value of one of its attributes 49 | response = self.client.storage_pool.get(self.storage_pool_1_id) 50 | self.assertNotEqual(response['name'], new_name) 51 | self.assertFalse(response['isProtected']) 52 | self.assertFalse(response['isColdStorageEnabled']) 53 | 54 | # Update the attribute and check the response 55 | response = self.client.storage_pool.update(self.storage_pool_1_id, 56 | name=new_name, 57 | is_protected=True) 58 | self.assertEqual(response['name'], new_name) 59 | self.assertTrue(response['isProtected']) 60 | 61 | def test_storage_pools_delete(self): 62 | self.client.storage_pool.delete(self.storage_pool_3_id) 63 | f = self.client.storage_pool.get 64 | self.assertRaises(ECSClientException, f, self.storage_pool_3_id) 65 | -------------------------------------------------------------------------------- /tests/functional/helper.py: -------------------------------------------------------------------------------- 1 | 2 | def get_sample_certificate(): 3 | return """-----BEGIN CERTIFICATE----- 4 | MIIDBjCCAe4CCQCV30YM7Vm0GTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB 5 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 6 | cyBQdHkgTHRkMB4XDTE3MDIxNDE0NDcwNVoXDTE4MDIxNDE0NDcwNVowRTELMAkG 7 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 8 | IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 9 | AMFZgwssBciWm70iLx9S8XsbY2cF4gS4LZkcAwUdiBQ+t/8hHGeFrM2vT4CCVyCs 10 | i2eLDbNVPDsYi+8G7MI4Tcok7bjGaJbXjw6mxc1v2d3dkpxbmEYEQ8esWXxgf25b 11 | 6lVirZScF133lyQmWCuUjBMDkPvd3zVI3ddOK7V7xqaaS6ZZ/WRCtG0RJDBWGm66 12 | TkOXlVoNQQKiyGuS30fP1Pjl1p05JytyVo16LskU/j70uf3x1TvPTGuR1/peMfsh 13 | nSXMT1gw98yWrV3xFmDGpyMs8IVM5FKqLtSdkny265VajjTh4r6JJtyl0hHw2UKg 14 | 3nYWB+JVroBOFPALW1mKVzECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAsDIDwX1p 15 | VwyI0ScgFJA5cMCUJVZRO/xzV0eTWg1n4O/+H2mRLO+m95o0kVuubt2JzfC98cfS 16 | 6Oo9WEMehoJHQ/FhlpQKrwqdqwJUwE/qa+bkwi9TqZzBHpyoSKrq9zhhS7c95dhC 17 | WFCB8GRyoAhd/mgFSyxdaYpie0BMEViWLcAJmtob54MeFLiHEo2MxSFoBLFwpL18 18 | 9i6oTjryfB2Wo66bJFHVECcPEnijgKPROvAqBoKMfDv2mKGBLovzklK5oSLxWfqK 19 | cbs3pJwZDHn/js9jxi+g+QTQtfs+adTHPt3udI3t1OO0JvkiB7waa2ZhD0gRnLYZ 20 | ra3G3SUr6zxvag== 21 | -----END CERTIFICATE----- 22 | """ 23 | 24 | 25 | def get_sample_private_key(): 26 | return """-----BEGIN RSA PRIVATE KEY----- 27 | MIIEpAIBAAKCAQEAwVmDCywFyJabvSIvH1LxextjZwXiBLgtmRwDBR2IFD63/yEc 28 | Z4Wsza9PgIJXIKyLZ4sNs1U8OxiL7wbswjhNyiTtuMZoltePDqbFzW/Z3d2SnFuY 29 | RgRDx6xZfGB/blvqVWKtlJwXXfeXJCZYK5SMEwOQ+93fNUjd104rtXvGpppLpln9 30 | ZEK0bREkMFYabrpOQ5eVWg1BAqLIa5LfR8/U+OXWnTknK3JWjXouyRT+PvS5/fHV 31 | O89Ma5HX+l4x+yGdJcxPWDD3zJatXfEWYManIyzwhUzkUqou1J2SfLbrlVqONOHi 32 | vokm3KXSEfDZQqDedhYH4lWugE4U8AtbWYpXMQIDAQABAoIBAHziLgwXQR0QekMt 33 | QzZ+Qk77n7a5TAU0WO9d8m1pZ6173ShC4K/U3Yp0w2sDxPbru5cDb7P3R9H7xtZ6 34 | qI3CBCyesZT5QjC7xkD2YyiQKC5voPSxDKectZLQtXPqeCR15eOEuw0TzNv6wXqt 35 | 8aEkb10QnqKv6i3mhLM+YGj2K5cLTk5dS8ry0HRYn8qUhW8Z0uGc4+/xjuSGP6YQ 36 | 96u0Dg/xtjT6cuyXQXJ1FxPrutTUJELChLJzEg4omTCK5IdX1jyMFF01tCMsLqfO 37 | kz2NTtgdj/bVCE+yH3KDd3zfr0O3mQzQpuzHV0HDs59nwCi0L4sVDcd5/uUl6g7c 38 | AXnji2ECgYEA5/hZrzTfVgyncalti3ZmjQB9PC6LMiDhxuMGLd50bnZW0Ml/hwUS 39 | juUE8cjiuwiBi9XFrLhUYqc9AVgd+iBrWuJkPqey69+i2C+7jRH/cHzQDZcP3biJ 40 | fNRnz7RhEhPZxgEUotKJ21oUi2/yY/IMH7khF4HdUQAcu3qvC59jFnUCgYEA1WD6 41 | 1lqmXnEx4EbZXdQbFoCJe+QTTqYFDcvYSoYPqQK/Xgjw6hhavGMG4d4bQ7q7W/sp 42 | ICZYvZAQI+eX8J90RMbetDATHV+/lNtlc8C89ELvlFgajxg+hyF3iS6YX6wPyzxd 43 | /D09n1+FbRPXDc+zuFHiZDYvlbBy3PetLfUTfk0CgYEA0ucgm0Rl/fVZU+ToHRFU 44 | IcSdGFd05OmyfMIx/wgvMbNUMVXSJa899T9R5IZxZf1Q0xNUOzoINv24YH1+G8jX 45 | 8nS9EZ4k9YGJrCaRzavcY6iG/KQon7zE/AKeM+DbFLWJnRWkbnkfL5jR7wikB6ys 46 | yzblWFsRHjEM7oOXnOiYDXUCgYA6vs9wUla5a5qar4bKkUm/4TV4uWsjXQvJJ5Tt 47 | t4j6M/pxri59lKTudqj/aEHDXNaWVUiWRFhsIAyBhhJLueqtwL6xhwbMMH8zQU/m 48 | FWswKHmDMtplCPy2AV3Ck+R5eoqCnqUVe9rKVOcMB8lOUR+eoFSe2Fgzcve8pCe2 49 | nmF2hQKBgQCBzSq3nug2+W0xPGZ1SD4Ay6HDys0TtBYgL2PJ1hwzVz7wvrPGnMV9 50 | u+R0C5UqsCfdMFbv9udKVzPBI6logSdfff15L9u5gJ1W2p0PuQk5SaU5Tbg32nbW 51 | SlPRoJa67LAJvzss9jjKp0U3Ttlh1GyDI0iypQGXIo3gHrxl6g4utg== 52 | -----END RSA PRIVATE KEY----- 53 | """ 54 | -------------------------------------------------------------------------------- /ecsclient/v2/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ecsclient import baseclient 4 | from ecsclient.v2.cas import cas 5 | from ecsclient.v2.configuration import certificate, configuration_properties, licensing 6 | from ecsclient.v2.configuration import feature 7 | from ecsclient.v2.geo_replication import replication_group, temporary_failed_zone 8 | from ecsclient.v2.provisioning import base_url, bucket, data_store, storage_pool, virtual_data_center, node, \ 9 | vdc_keystore 10 | from ecsclient.v2.metering import billing 11 | from ecsclient.v2.monitoring import capacity, dashboard, events, alerts 12 | from ecsclient.v2.multitenancy import namespace 13 | from ecsclient.v2.user_management import authentication_provider, management_user, object_user, secret_key 14 | from ecsclient.v2.other import user_info 15 | 16 | 17 | # Initialize logger 18 | log = logging.getLogger(__name__) 19 | 20 | 21 | class Client(baseclient.Client): 22 | version = 'v2' 23 | 24 | def __init__(self, *args, **kwargs): 25 | super(Client, self).__init__(*args, **kwargs) 26 | 27 | # Configuration 28 | self.certificate = certificate.Certificate(self) 29 | self.configuration_properties = configuration_properties.ConfigurationProperties(self) 30 | self.licensing = licensing.Licensing(self) 31 | self.feature = feature.Feature(self) 32 | 33 | # CAS 34 | self.cas = cas.Cas(self) 35 | 36 | # File system access 37 | # TODO: self.nfs = nfs.NFS(self) 38 | 39 | # Metering 40 | self.billing = billing.Billing(self) 41 | 42 | # Migration 43 | # TODO: self.transformation = transformation.Transformation(self) 44 | 45 | # Monitoring 46 | self.capacity = capacity.Capacity(self) 47 | self.dashboard = dashboard.Dashboard(self) 48 | self.events = events.Events(self) 49 | self.alerts = alerts.Alerts(self) 50 | 51 | # Multi-tenancy 52 | self.namespace = namespace.Namespace(self) 53 | 54 | # Geo-replication 55 | self.replication_group = replication_group.ReplicationGroup(self) 56 | self.temp_failed_zone = temporary_failed_zone.TemporaryFailedZone(self) 57 | 58 | # Provisioning 59 | self.base_url = base_url.BaseUrl(self) 60 | self.bucket = bucket.Bucket(self) 61 | self.data_store = data_store.DataStore(self) 62 | self.node = node.Node(self) 63 | self.storage_pool = storage_pool.StoragePool(self) 64 | self.vdc = virtual_data_center.VirtualDataCenter(self) 65 | self.vdc_keystore = vdc_keystore.VdcKeystore(self) 66 | 67 | # Support 68 | # TODO: self.call_home = call_home.CallHome(self) 69 | 70 | # User Management 71 | self.authentication_provider = authentication_provider.AuthenticationProvider(self) 72 | # TODO: self.password_group = password_group.PasswordGroup(self) 73 | self.secret_key = secret_key.SecretKey(self) 74 | self.management_user = management_user.ManagementUser(self) 75 | self.object_user = object_user.ObjectUser(self) 76 | 77 | # Other 78 | self.user_info = user_info.UserInfo(self) 79 | -------------------------------------------------------------------------------- /ecsclient/common/user_management/authentication_provider.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class AuthenticationProvider(object): 15 | 16 | def __init__(self, connection): 17 | """ 18 | Initialize a new instance 19 | """ 20 | self.conn = connection 21 | 22 | def get_authentication_providers(self): 23 | """ 24 | Lists the configured authentication providers. 25 | 26 | Required role(s): 27 | 28 | SYSTEM_ADMIN 29 | SYSTEM_MONITOR 30 | 31 | Example JSON result from the API: 32 | 33 | { 34 | u'authnprovider': [ 35 | { 36 | u'id': u'urn: AuthProvider: 37 | fe4843f3-71eb-4a86-b1bd-806f4275998b', 38 | u'name': u'internal' 39 | } 40 | ] 41 | } 42 | """ 43 | log.info('Getting all authentication providers') 44 | return self.conn.get(url='vdc/admin/authnproviders') 45 | 46 | def get_authentication_provider(self, auth_provider_id): 47 | """ 48 | Lists the configured authentication providers. 49 | 50 | Required role(s): 51 | 52 | SYSTEM_ADMIN 53 | SYSTEM_MONITOR 54 | 55 | Example JSON result from the API: 56 | 57 | { 58 | u'search_scope': u'ONELEVEL', 59 | u'group_whitelist_values': [ 60 | 61 | ], 62 | u'remote': None, 63 | u'name': u'internal', 64 | u'tags': [ 65 | 66 | ], 67 | u'manager_dn': u'uid=admin, 68 | cn=users, 69 | cn=accounts, 70 | dc=osaas-lab, 71 | dc=rcsops, 72 | dc=com', 73 | u'max_page_size': 0, 74 | u'search_base': u'cn=users, 75 | cn=accounts, 76 | dc=osaas-lab, 77 | dc=rcsops, 78 | dc=com', 79 | u'global': None, 80 | u'group_attribute': u'CN', 81 | u'server_urls': [ 82 | u'ldaps: //192.29.62.18: 636/', 83 | u'ldaps: //192.29.62.83: 636/', 84 | u'ldaps: //192.29.62.84: 636/', 85 | u'ldaps: //192.29.62.19: 636/' 86 | ], 87 | u'vdc': None, 88 | u'disable': False, 89 | u'mode': u'ldap', 90 | u'domains': [ 91 | u'tenantadmins', 92 | u'internal' 93 | ], 94 | u'search_filter': u'uid=%U', 95 | u'id': u'urn: AuthProvider: 317843ad-71eb-4a86-b1bd-806f4275008a', 96 | u'description': u'internal' 97 | } 98 | 99 | param: auth_provider_id: Authentication provider identifier URN 100 | """ 101 | log.info("Getting auth provider '{0}'".format(auth_provider_id)) 102 | 103 | return self.conn.get( 104 | url='vdc/admin/authnproviders/{0}'.format(auth_provider_id)) 105 | -------------------------------------------------------------------------------- /tests/functional/test_object_user.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from ecsclient.common.exceptions import ECSClientException 5 | from tests import functional 6 | 7 | 8 | class TestObjectUser(functional.BaseTestCase): 9 | def __init__(self, *args, **kwargs): 10 | super(TestObjectUser, self).__init__(*args, **kwargs) 11 | self.namespace_1 = "functional-tests-namespace-%s" % int(time.time()) 12 | self.object_user_1 = "functional-tests-objectuser-%s" % int(time.time()) 13 | self.object_user_2 = self.object_user_1 + '_second' 14 | 15 | def setUp(self): 16 | super(TestObjectUser, self).setUp() 17 | self.client.namespace.create(self.namespace_1) 18 | self.client.object_user.create(self.object_user_1, self.namespace_1) 19 | 20 | def tearDown(self): 21 | super(TestObjectUser, self).tearDown() 22 | for object_user in [self.object_user_1, 23 | self.object_user_2]: 24 | try: 25 | self.client.object_user.delete(object_user) 26 | except ECSClientException: 27 | pass 28 | 29 | self.client.namespace.delete(self.namespace_1) 30 | 31 | def test_object_user_list(self): 32 | response = self.client.object_user.list() 33 | self.assertValidSchema(response, schemas.OBJECT_USERS) 34 | 35 | def test_object_user_list_with_namespace(self): 36 | response = self.client.object_user.list(namespace=self.namespace_1) 37 | self.assertValidSchema(response, schemas.OBJECT_USERS) 38 | 39 | def test_object_user_get(self): 40 | response = self.client.object_user.get(self.object_user_1) 41 | self.assertValidSchema(response, schemas.OBJECT_USER) 42 | 43 | def test_object_user_get_with_namespace(self): 44 | response = self.client.object_user.get(self.object_user_1, namespace=self.namespace_1) 45 | self.assertValidSchema(response, schemas.OBJECT_USER) 46 | 47 | def test_object_user_create(self): 48 | response = self.client.object_user.create(self.object_user_2, self.namespace_1) 49 | self.assertValidSchema(response, {"type": "object", "properties": {"link": schemas.LINK}, "required": ["link"]}) 50 | 51 | def test_object_user_delete(self): 52 | self.client.object_user.delete(self.object_user_1, namespace=self.namespace_1) 53 | f = self.client.object_user.get 54 | self.assertRaises(ECSClientException, f, self.object_user_1, namespace=self.namespace_1) 55 | 56 | def test_object_user_lock_unlock(self): 57 | r = self.client.object_user.get_lock(self.object_user_1, namespace=self.namespace_1) 58 | self.assertEqual(r['isLocked'], False) 59 | 60 | self.client.object_user.lock(self.object_user_1, namespace=self.namespace_1) 61 | 62 | r = self.client.object_user.get_lock(self.object_user_1, namespace=self.namespace_1) 63 | self.assertEqual(r['isLocked'], True) 64 | 65 | self.client.object_user.unlock(self.object_user_1, namespace=self.namespace_1) 66 | 67 | r = self.client.object_user.get_lock(self.object_user_1, namespace=self.namespace_1) 68 | self.assertEqual(r['isLocked'], False) 69 | -------------------------------------------------------------------------------- /ecsclient/v3/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ecsclient import baseclient 4 | from ecsclient.v3.configuration import certificate, configuration_properties, \ 5 | licensing, feature, syslog, snmp 6 | from ecsclient.v3.cas import cas 7 | from ecsclient.v3.metering import billing 8 | from ecsclient.v3.monitoring import capacity, dashboard, events, alerts 9 | from ecsclient.v3.multitenancy import namespace 10 | from ecsclient.v3.geo_replication import replication_group, temporary_failed_zone 11 | from ecsclient.v3.provisioning import base_url, bucket, data_store, storage_pool, \ 12 | virtual_data_center, node, vdc_keystore 13 | from ecsclient.v3.user_management import authentication_provider, management_user, \ 14 | object_user, secret_key, password_group 15 | from ecsclient.v3.other import user_info 16 | 17 | # Initialize logger 18 | log = logging.getLogger(__name__) 19 | 20 | 21 | class Client(baseclient.Client): 22 | version = 'v3' 23 | 24 | def __init__(self, *args, **kwargs): 25 | super(Client, self).__init__(*args, **kwargs) 26 | 27 | # Configuration 28 | self.certificate = certificate.Certificate(self) 29 | self.configuration_properties = configuration_properties.ConfigurationProperties(self) 30 | self.licensing = licensing.Licensing(self) 31 | self.feature = feature.Feature(self) 32 | self.syslog = syslog.Syslog(self) 33 | self.snmp = snmp.Snmp(self) 34 | 35 | # CAS 36 | self.cas = cas.Cas(self) 37 | 38 | # File system access 39 | # TODO: self.nfs = nfs.NFS(self) 40 | 41 | # Metering 42 | self.billing = billing.Billing(self) 43 | 44 | # Migration 45 | # TODO: self.transformation = transformation.Transformation(self) 46 | 47 | # Monitoring 48 | self.capacity = capacity.Capacity(self) 49 | self.dashboard = dashboard.Dashboard(self) 50 | self.events = events.Events(self) 51 | self.alerts = alerts.Alerts(self) 52 | 53 | # Multi-tenancy 54 | self.namespace = namespace.Namespace(self) 55 | # Geo-replication 56 | self.replication_group = replication_group.ReplicationGroup(self) 57 | self.temporary_failed_zone = temporary_failed_zone.TemporaryFailedZone(self) 58 | 59 | # Provisioning 60 | self.base_url = base_url.BaseUrl(self) 61 | self.bucket = bucket.Bucket(self) 62 | self.data_store = data_store.DataStore(self) 63 | self.node = node.Node(self) 64 | self.storage_pool = storage_pool.StoragePool(self) 65 | self.vdc = virtual_data_center.VirtualDataCenter(self) 66 | self.vdc_keystore = vdc_keystore.VdcKeystore(self) 67 | 68 | # Support 69 | # TODO: self.call_home = call_home.CallHome(self) 70 | 71 | # User Management 72 | self.authentication_provider = authentication_provider.AuthenticationProvider(self) 73 | self.password_group = password_group.PasswordGroup(self) 74 | self.secret_key = secret_key.SecretKey(self) 75 | self.management_user = management_user.ManagementUser(self) 76 | self.object_user = object_user.ObjectUser(self) 77 | 78 | # Other 79 | self.user_info = user_info.UserInfo(self) 80 | -------------------------------------------------------------------------------- /ecsclient/v4/client.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ecsclient import baseclient 4 | from ecsclient.v4.configuration import certificate, configuration_properties, \ 5 | licensing, feature, syslog, snmp 6 | from ecsclient.v4.cas import cas 7 | from ecsclient.v4.metering import billing 8 | from ecsclient.v4.monitoring import capacity, dashboard, events, alerts 9 | from ecsclient.v4.multitenancy import namespace 10 | from ecsclient.v4.multitenancy import tenant 11 | from ecsclient.v4.geo_replication import replication_group, temporary_failed_zone 12 | from ecsclient.v4.provisioning import base_url, bucket, data_store, storage_pool, \ 13 | virtual_data_center, node, vdc_keystore 14 | from ecsclient.v4.user_management import authentication_provider, management_user, \ 15 | object_user, secret_key, password_group 16 | from ecsclient.v4.other import user_info 17 | 18 | # Initialize logger 19 | log = logging.getLogger(__name__) 20 | 21 | 22 | class Client(baseclient.Client): 23 | version = 'v4' 24 | 25 | def __init__(self, *args, **kwargs): 26 | super(Client, self).__init__(*args, **kwargs) 27 | 28 | # Configuration 29 | self.certificate = certificate.Certificate(self) 30 | self.configuration_properties = configuration_properties.ConfigurationProperties(self) 31 | self.licensing = licensing.Licensing(self) 32 | self.feature = feature.Feature(self) 33 | self.syslog = syslog.Syslog(self) 34 | self.snmp = snmp.Snmp(self) 35 | 36 | # CAS 37 | self.cas = cas.Cas(self) 38 | 39 | # File system access 40 | # TODO: self.nfs = nfs.NFS(self) 41 | 42 | # Metering 43 | self.billing = billing.Billing(self) 44 | 45 | # Migration 46 | # TODO: self.transformation = transformation.Transformation(self) 47 | 48 | # Monitoring 49 | self.capacity = capacity.Capacity(self) 50 | self.dashboard = dashboard.Dashboard(self) 51 | self.events = events.Events(self) 52 | self.alerts = alerts.Alerts(self) 53 | 54 | # Multi-tenancy 55 | self.namespace = namespace.Namespace(self) 56 | self.tenant = tenant.Tenant(self) 57 | # Geo-replication 58 | self.replication_group = replication_group.ReplicationGroup(self) 59 | self.temporary_failed_zone = temporary_failed_zone.TemporaryFailedZone(self) 60 | 61 | # Provisioning 62 | self.base_url = base_url.BaseUrl(self) 63 | self.bucket = bucket.Bucket(self) 64 | self.data_store = data_store.DataStore(self) 65 | self.node = node.Node(self) 66 | self.storage_pool = storage_pool.StoragePool(self) 67 | self.vdc = virtual_data_center.VirtualDataCenter(self) 68 | self.vdc_keystore = vdc_keystore.VdcKeystore(self) 69 | 70 | # Support 71 | # TODO: self.call_home = call_home.CallHome(self) 72 | 73 | # User Management 74 | self.authentication_provider = authentication_provider.AuthenticationProvider(self) 75 | self.password_group = password_group.PasswordGroup(self) 76 | self.secret_key = secret_key.SecretKey(self) 77 | self.management_user = management_user.ManagementUser(self) 78 | self.object_user = object_user.ObjectUser(self) 79 | 80 | # Other 81 | self.user_info = user_info.UserInfo(self) 82 | -------------------------------------------------------------------------------- /tests/unit/test_exception.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import testtools 3 | from requests_mock.contrib import fixture 4 | 5 | from ecsclient.common.exceptions import ECSClientException 6 | 7 | 8 | class TestException(testtools.TestCase): 9 | TEST_URL = 'https://127.0.0.1:4443/foor/bar?param1=value1¶m2=value2' 10 | 11 | def setUp(self): 12 | super(TestException, self).setUp() 13 | self.requests_mock = self.useFixture(fixture.Fixture()) 14 | 15 | def test_is_exception(self): 16 | self.assertTrue(issubclass(ECSClientException, Exception)) 17 | 18 | def test_attrs(self): 19 | test_kwargs = { 20 | 'ecs_code': '21000', 21 | 'ecs_retryable': True, 22 | 'ecs_description': 'Error description', 23 | 'ecs_details': 'Error details', 24 | 'http_scheme': 'https', 25 | 'http_host': 'localhost', 26 | 'http_port': 4443, 27 | 'http_path': '/path', 28 | 'http_query': 'param1=value1', 29 | 'http_status': 500, 30 | 'http_reason': 'Reason', 31 | 'http_response_content': 'Response content', 32 | 'http_response_headers': [{'x-header-1': 'value1'}] 33 | } 34 | exc = ECSClientException('test', **test_kwargs) 35 | 36 | for key, value in test_kwargs.items(): 37 | self.assertIs(True, hasattr(exc, key)) 38 | self.assertEqual(getattr(exc, key), value) 39 | 40 | def test_format(self): 41 | test_kwargs = { 42 | 'ecs_description': 'Error description', 43 | 'ecs_details': 'Error details', 44 | 'http_scheme': 'https', 45 | 'http_host': 'localhost', 46 | 'http_port': 4443, 47 | 'http_path': '/path', 48 | 'http_query': 'param1=value1', 49 | 'http_status': 500, 50 | 'http_reason': 'Reason', 51 | 'http_response_content': 'Response content' 52 | } 53 | 54 | for key, value in test_kwargs.items(): 55 | kwargs = {key: value} 56 | exc = ECSClientException('test', **kwargs) 57 | self.assertIn(str(value), str(exc)) 58 | 59 | def test_from_response(self): 60 | response_content = '{"code": "1000", "retryable": false, ' \ 61 | '"description": "Error description", ' \ 62 | '"details": "Error details"}' 63 | self.requests_mock.register_uri('POST', self.TEST_URL, 64 | status_code=500, 65 | text=response_content) 66 | resp = requests.post(self.TEST_URL, data='body') 67 | exc = ECSClientException.from_response(resp) 68 | 69 | self.assertEqual(exc.ecs_code, '1000') 70 | self.assertEqual(exc.ecs_retryable, False) 71 | self.assertEqual(exc.ecs_description, 'Error description') 72 | self.assertEqual(exc.ecs_details, 'Error details') 73 | self.assertEqual(exc.http_scheme, 'https') 74 | self.assertEqual(exc.http_host, '127.0.0.1') 75 | self.assertEqual(exc.http_port, 4443) 76 | self.assertEqual(exc.http_path, '/foor/bar') 77 | self.assertEqual(exc.http_query, 'param1=value1¶m2=value2') 78 | self.assertEqual(exc.http_status, 500) 79 | self.assertEqual(exc.http_reason, None) 80 | self.assertEqual(exc.http_response_content, response_content) 81 | -------------------------------------------------------------------------------- /tests/functional/test_management_user.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from ecsclient.common.exceptions import ECSClientException 5 | from tests import functional 6 | 7 | 8 | class TestManagementUser(functional.BaseTestCase): 9 | def __init__(self, *args, **kwargs): 10 | super(TestManagementUser, self).__init__(*args, **kwargs) 11 | self.management_user_1 = "functional-tests-managementuser-%s" % int(time.time()) 12 | self.management_user_2 = self.management_user_1 + '_second' 13 | 14 | def setUp(self): 15 | super(TestManagementUser, self).setUp() 16 | self.client.management_user.create(self.management_user_1, 17 | password='fake-password-123') 18 | 19 | def tearDown(self): 20 | super(TestManagementUser, self).tearDown() 21 | for management_user in [self.management_user_1, 22 | self.management_user_2]: 23 | try: 24 | self.client.management_user.delete(management_user) 25 | except ECSClientException: 26 | pass 27 | 28 | def test_management_user_list(self): 29 | response = self.client.management_user.list() 30 | self.assertValidSchema(response, schemas.MANAGEMENT_USERS) 31 | 32 | def test_management_user_get(self): 33 | response = self.client.management_user.get(self.management_user_1) 34 | self.assertValidSchema(response, schemas.MANAGEMENT_USER) 35 | self.assertEqual(response['userId'], self.management_user_1) 36 | 37 | def test_management_user_create(self): 38 | response = self.client.management_user.create(self.management_user_2, 39 | password='fake-password-123', 40 | is_system_admin=True, 41 | is_system_monitor=True, 42 | is_security_admin=True) 43 | self.assertValidSchema(response, schemas.MANAGEMENT_USER) 44 | self.assertEqual(response['userId'], self.management_user_2) 45 | self.assertTrue(response['isSystemAdmin']) 46 | self.assertTrue(response['isSystemMonitor']) 47 | self.assertTrue(response['isSecurityAdmin']) 48 | 49 | def test_management_user_delete(self): 50 | self.client.management_user.delete(self.management_user_1) 51 | f = self.client.management_user.get 52 | self.assertRaises(ECSClientException, f, self.management_user_1) 53 | 54 | def test_management_user_update(self): 55 | response = self.client.management_user.get(self.management_user_1) 56 | self.assertFalse(response['isSystemAdmin']) 57 | self.assertFalse(response['isSystemMonitor']) 58 | self.assertFalse(response['isSecurityAdmin']) 59 | 60 | self.client.management_user.update(self.management_user_1, 61 | password='fake-password-123', 62 | is_system_admin=True, 63 | is_system_monitor=True, 64 | is_security_admin=True) 65 | 66 | response = self.client.management_user.get(self.management_user_1) 67 | self.assertTrue(response['isSystemAdmin']) 68 | self.assertTrue(response['isSystemMonitor']) 69 | self.assertTrue(response['isSecurityAdmin']) 70 | -------------------------------------------------------------------------------- /tests/unit/test_node.py: -------------------------------------------------------------------------------- 1 | import testtools 2 | from requests_mock.contrib import fixture 3 | from mock import MagicMock 4 | from mock import mock 5 | from six.moves import http_client 6 | from ecsclient.client import Client 7 | from ecsclient.common.exceptions import ECSClientException 8 | 9 | 10 | class TestNode(testtools.TestCase): 11 | 12 | def setUp(self): 13 | super(TestNode, self).setUp() 14 | self.ecs_endpoint = 'https://127.0.0.1:4443' 15 | self.token_endpoint = 'https://127.0.0.1:4443/login' 16 | 17 | self.client = Client( 18 | '2', 19 | ecs_endpoint=self.ecs_endpoint, 20 | token_endpoint=self.token_endpoint, 21 | username='user', 22 | password='password' 23 | ) 24 | 25 | self.returned_json = { 26 | "node": [ 27 | { 28 | "ip": "172.29.3.148", 29 | "version": "1.2.0.0.60071.ffbe16c", 30 | "rackId": "gray", 31 | "nodename": "supr01-r01-01.lax01s1.rspaas-lab.ops.com", 32 | "nodeid": "171.29.3.140" 33 | }, 34 | { 35 | "ip": "172.29.3.149", 36 | "version": "1.2.0.0.60071.ffbe16c", 37 | "rackId": "gray", 38 | "nodename": "supr01-r01-02.lax01s1.rspaas-lab.ops.com", 39 | "nodeid": "171.29.3.141" 40 | } 41 | ] 42 | } 43 | 44 | self.response = MagicMock() 45 | self.requests_mock = self.useFixture(fixture.Fixture()) 46 | 47 | @mock.patch('ecsclient.common.token_request.TokenRequest.get_token') 48 | def test_list_nodes_throw_exception(self, mock_get_token): 49 | self.requests_mock.register_uri('GET', 'https://127.0.0.1:4443/vdc/nodes', 50 | status_code=http_client.INTERNAL_SERVER_ERROR, 51 | text='Server Error') 52 | mock_get_token.return_value = 'FAKE-TOKEN-123' 53 | 54 | with super(testtools.TestCase, self).assertRaises(ECSClientException) as error: 55 | self.client.node.list() 56 | 57 | exception = error.exception 58 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 59 | self.assertEqual(self.requests_mock.last_request.url, 'https://127.0.0.1:4443/vdc/nodes') 60 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'FAKE-TOKEN-123') 61 | self.assertEqual(exception.http_response_content, 'Server Error') 62 | self.assertEqual(exception.http_status, http_client.INTERNAL_SERVER_ERROR) 63 | 64 | @mock.patch('ecsclient.common.token_request.TokenRequest.get_token') 65 | def test_list_nodes(self, mock_get_token): 66 | mock_get_token.return_value = 'FAKE-TOKEN-123' 67 | self.requests_mock.register_uri('GET', 'https://127.0.0.1:4443/vdc/nodes', 68 | status_code=http_client.OK, 69 | json=self.returned_json) 70 | 71 | response = self.client.node.list() 72 | 73 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 74 | self.assertEqual(self.requests_mock.last_request.url, 'https://127.0.0.1:4443/vdc/nodes') 75 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'FAKE-TOKEN-123') 76 | self.assertEqual(response, self.returned_json) 77 | -------------------------------------------------------------------------------- /tests/unit/test_http.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import testtools 4 | from requests_mock.contrib import fixture 5 | from six import string_types 6 | 7 | from ecsclient.client import Client 8 | 9 | 10 | class TestHttp(testtools.TestCase): 11 | 12 | TEST_URL = 'http://127.0.0.1:4443/hi' 13 | 14 | def __init__(self, *args, **kwargs): 15 | super(TestHttp, self).__init__(*args, **kwargs) 16 | self.client = Client('3', 17 | username='user', 18 | password='password', 19 | token='token', 20 | ecs_endpoint='http://127.0.0.1:4443', 21 | token_endpoint='http://127.0.0.1:4443/login') 22 | 23 | def setUp(self): 24 | super(TestHttp, self).setUp() 25 | self.requests_mock = self.useFixture(fixture.Fixture()) 26 | 27 | def test_get_request_plaintext_resp(self): 28 | self.requests_mock.register_uri('GET', self.TEST_URL, text='resp') 29 | 30 | body = self.client.get('hi', params={'key1': 'value1'}) 31 | 32 | self.assertIsInstance(body, string_types) 33 | self.assertEqual('resp', body) 34 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 35 | self.assertEqual(self.requests_mock.last_request.url, self.TEST_URL + '?key1=value1') 36 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'token') 37 | 38 | def test_get_request_json_resp(self): 39 | self.requests_mock.register_uri('GET', self.TEST_URL, text='{"key2": "value2"}') 40 | 41 | body = self.client.get('hi', params={'key1': 'value1'}) 42 | 43 | self.assertIsInstance(body, dict) 44 | self.assertEqual(body['key2'], "value2") 45 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 46 | self.assertEqual(self.requests_mock.last_request.url, self.TEST_URL + '?key1=value1') 47 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'token') 48 | 49 | def test_post_request(self): 50 | self.requests_mock.register_uri('POST', self.TEST_URL, text=None) 51 | 52 | payload = {'key1': 'value1', 'key2': 'value2'} 53 | self.client.post('hi', json_payload=payload) 54 | 55 | self.assertEqual(self.requests_mock.last_request.method, 'POST') 56 | self.assertEqual(json.loads(self.requests_mock.last_request.text), payload) 57 | self.assertEqual(self.requests_mock.last_request.url, self.TEST_URL) 58 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'token') 59 | 60 | def test_put_request(self): 61 | self.requests_mock.register_uri('PUT', self.TEST_URL, text=None) 62 | 63 | payload = {'key1': 'value1', 'key2': 'value2'} 64 | self.client.put('hi', json_payload=payload) 65 | 66 | self.assertEqual(self.requests_mock.last_request.method, 'PUT') 67 | self.assertEqual(json.loads(self.requests_mock.last_request.text), payload) 68 | self.assertEqual(self.requests_mock.last_request.url, self.TEST_URL) 69 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'token') 70 | 71 | def test_delete_request(self): 72 | self.requests_mock.register_uri('DELETE', self.TEST_URL, text=None) 73 | 74 | self.client.delete('hi') 75 | 76 | self.assertEqual(self.requests_mock.last_request.method, 'DELETE') 77 | self.assertEqual(self.requests_mock.last_request.url, self.TEST_URL) 78 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'token') -------------------------------------------------------------------------------- /ecsclient/v3/configuration/syslog.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Syslog(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get_syslog_servers(self): 14 | """ 15 | Get list of Syslog Server info. 16 | 17 | Required role(s): 18 | 19 | SYSTEM_ADMIN 20 | SYSTEM_MONITOR 21 | 22 | Example JSON result from the API: 23 | """ 24 | # TODO: Add example JSON response 25 | log.info("Fetching syslog servers") 26 | return self.conn.get('vdc/syslog/config') 27 | 28 | def get_syslog_server(self, syslog_id): 29 | """ 30 | Get Syslog Server info. 31 | 32 | Required role(s): 33 | 34 | SYSTEM_ADMIN 35 | SYSTEM_MONITOR 36 | 37 | Example JSON result from the API: 38 | {} 39 | 40 | :param syslog_id: Syslog server ID 41 | """ 42 | # TODO: Add example JSON response 43 | log.info("Getting syslog server information with ID '{}'".format(syslog_id)) 44 | return self.conn.get('vdc/syslog/config/{}'.format(syslog_id)) 45 | 46 | def create_syslog_server(self, server, port, protocol, severity): 47 | """ 48 | Creates a Syslog server. 49 | 50 | Required role(s): 51 | 52 | SYSTEM_ADMIN 53 | 54 | Example JSON result from the API: 55 | {} 56 | 57 | :param server: Fully qualified domain name or IP 58 | :param port: Syslog port 59 | :param protocol: Protocol Syslog protocol UDP/TCP 60 | :param severity: Severity - minimal syslog message severity for this server 61 | """ 62 | # TODO: Add example JSON response 63 | 64 | payload = { 65 | "server": server, 66 | "port": port, 67 | "protocol": protocol, 68 | "severity": severity 69 | } 70 | 71 | log.info("Creating syslog server") 72 | return self.conn.post('vdc/syslog/config', json_payload=payload) 73 | 74 | def update_syslog_server(self, syslog_id, server, port, protocol, severity): 75 | """ 76 | Update specified Syslog Server. 77 | 78 | Required role(s): 79 | 80 | SYSTEM_ADMIN 81 | SYSTEM_MONITOR 82 | 83 | There is no response body for this call 84 | 85 | Expect: HTTP/1.1 200 OK 86 | 87 | :param syslog_id: Syslog server ID 88 | :param server: Fully qualified domain name or IP 89 | :param port: Syslog port 90 | :param protocol: Protocol Syslog protocol UDP/TCP 91 | :param severity: Severity - minimal syslog message severity for this server 92 | """ 93 | 94 | payload = { 95 | "server": server, 96 | "port": port, 97 | "protocol": protocol, 98 | "severity": severity 99 | } 100 | log.info("Updating syslog server with ID '{}'".format(syslog_id)) 101 | return self.conn.put('vdc/syslog/config/{}'.format(syslog_id), json_payload=payload) 102 | 103 | def delete_syslog_server(self, syslog_id): 104 | """ 105 | Delete specified Syslog Server. 106 | 107 | Required role(s): 108 | 109 | SYSTEM_ADMIN 110 | SYSTEM_MONITOR 111 | 112 | There is no response body for this call 113 | 114 | Expect: HTTP/1.1 200 OK 115 | 116 | :param syslog_id: Syslog server ID 117 | """ 118 | log.info("Deleting syslog server with ID '{}'".format(syslog_id)) 119 | return self.conn.delete('vdc/syslog/config/{}'.format(syslog_id)) 120 | -------------------------------------------------------------------------------- /ecsclient/v4/configuration/syslog.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Syslog(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get_syslog_servers(self): 14 | """ 15 | Get list of Syslog Server info. 16 | 17 | Required role(s): 18 | 19 | SYSTEM_ADMIN 20 | SYSTEM_MONITOR 21 | 22 | Example JSON result from the API: 23 | """ 24 | # TODO: Add example JSON response 25 | log.info("Fetching syslog servers") 26 | return self.conn.get('vdc/syslog/config') 27 | 28 | def get_syslog_server(self, syslog_id): 29 | """ 30 | Get Syslog Server info. 31 | 32 | Required role(s): 33 | 34 | SYSTEM_ADMIN 35 | SYSTEM_MONITOR 36 | 37 | Example JSON result from the API: 38 | {} 39 | 40 | :param syslog_id: Syslog server ID 41 | """ 42 | # TODO: Add example JSON response 43 | log.info("Getting syslog server information with ID '{}'".format(syslog_id)) 44 | return self.conn.get('vdc/syslog/config/{}'.format(syslog_id)) 45 | 46 | def create_syslog_server(self, server, port, protocol, severity): 47 | """ 48 | Creates a Syslog server. 49 | 50 | Required role(s): 51 | 52 | SYSTEM_ADMIN 53 | 54 | Example JSON result from the API: 55 | {} 56 | 57 | :param server: Fully qualified domain name or IP 58 | :param port: Syslog port 59 | :param protocol: Protocol Syslog protocol UDP/TCP 60 | :param severity: Severity - minimal syslog message severity for this server 61 | """ 62 | # TODO: Add example JSON response 63 | 64 | payload = { 65 | "server": server, 66 | "port": port, 67 | "protocol": protocol, 68 | "severity": severity 69 | } 70 | 71 | log.info("Creating syslog server") 72 | return self.conn.post('vdc/syslog/config', json_payload=payload) 73 | 74 | def update_syslog_server(self, syslog_id, server, port, protocol, severity): 75 | """ 76 | Update specified Syslog Server. 77 | 78 | Required role(s): 79 | 80 | SYSTEM_ADMIN 81 | SYSTEM_MONITOR 82 | 83 | There is no response body for this call 84 | 85 | Expect: HTTP/1.1 200 OK 86 | 87 | :param syslog_id: Syslog server ID 88 | :param server: Fully qualified domain name or IP 89 | :param port: Syslog port 90 | :param protocol: Protocol Syslog protocol UDP/TCP 91 | :param severity: Severity - minimal syslog message severity for this server 92 | """ 93 | 94 | payload = { 95 | "server": server, 96 | "port": port, 97 | "protocol": protocol, 98 | "severity": severity 99 | } 100 | log.info("Updating syslog server with ID '{}'".format(syslog_id)) 101 | return self.conn.put('vdc/syslog/config/{}'.format(syslog_id), json_payload=payload) 102 | 103 | def delete_syslog_server(self, syslog_id): 104 | """ 105 | Delete specified Syslog Server. 106 | 107 | Required role(s): 108 | 109 | SYSTEM_ADMIN 110 | SYSTEM_MONITOR 111 | 112 | There is no response body for this call 113 | 114 | Expect: HTTP/1.1 200 OK 115 | 116 | :param syslog_id: Syslog server ID 117 | """ 118 | log.info("Deleting syslog server with ID '{}'".format(syslog_id)) 119 | return self.conn.delete('vdc/syslog/config/{}'.format(syslog_id)) 120 | -------------------------------------------------------------------------------- /tests/functional/test_password_group.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from ecsclient.common.exceptions import ECSClientException 5 | from tests import functional 6 | 7 | 8 | class TestPasswordGroup(functional.BaseTestCase): 9 | def __init__(self, *args, **kwargs): 10 | super(TestPasswordGroup, self).__init__(*args, **kwargs) 11 | self.namespace_1 = "functional-tests-namespace-%s" % int(time.time()) 12 | self.object_user_1 = "functional-tests-objectuser-%s" % int(time.time()) 13 | self.object_user_2 = self.object_user_1 + '_second' 14 | self.password_1 = "fake-password-123" 15 | self.password_2 = "fake-password-456" 16 | self.groups_list_1 = ['admin'] 17 | self.groups_list_2 = ['admin', 'user'] 18 | 19 | def setUp(self): 20 | super(TestPasswordGroup, self).setUp() 21 | self.client.namespace.create(self.namespace_1) 22 | self.client.object_user.create(self.object_user_1, self.namespace_1) 23 | self.client.password_group.create(self.object_user_1, 24 | self.password_1, 25 | self.groups_list_1, 26 | namespace=self.namespace_1) 27 | 28 | def tearDown(self): 29 | super(TestPasswordGroup, self).tearDown() 30 | for object_user in [self.object_user_1, 31 | self.object_user_2]: 32 | try: 33 | self.client.object_user.delete(object_user) 34 | except ECSClientException: 35 | pass 36 | self.client.namespace.delete(self.namespace_1) 37 | 38 | def test_password_group_create_for_user(self): 39 | self.client.password_group.create(user_id=self.object_user_2, 40 | namespace=self.namespace_1, 41 | password=self.password_2, 42 | groups_list=self.groups_list_2) 43 | 44 | response = self.client.password_group.get(user_id=self.object_user_2, 45 | namespace=self.namespace_1) 46 | 47 | self.assertValidSchema(response, schemas.GROUP_LIST) 48 | self.assertEqual(response['groups_list'], self.groups_list_2) 49 | 50 | def test_password_group_get_by_user(self): 51 | response = self.client.password_group.get(user_id=self.object_user_1, 52 | namespace=self.namespace_1) 53 | 54 | self.assertValidSchema(response, schemas.GROUP_LIST) 55 | self.assertEqual(response['groups_list'], self.groups_list_1) 56 | 57 | def test_password_group_update_for_user(self): 58 | self.assertNotEqual(self.groups_list_1, self.groups_list_2) 59 | 60 | response = self.client.password_group.get(user_id=self.object_user_1, 61 | namespace=self.namespace_1) 62 | self.assertEqual(response['groups_list'], self.groups_list_1) 63 | 64 | self.client.password_group.update(user_id=self.object_user_1, 65 | namespace=self.namespace_1, 66 | password=self.password_2, 67 | groups_list=self.groups_list_2) 68 | 69 | response = self.client.password_group.get(user_id=self.object_user_1, 70 | namespace=self.namespace_1) 71 | self.assertEqual(response['groups_list'], self.groups_list_2) 72 | 73 | def test_password_group_delete_for_user(self): 74 | self.client.password_group.delete(user_id=self.object_user_1, 75 | namespace=self.namespace_1) 76 | f = self.client.password_group.get 77 | self.assertRaises(ECSClientException, f, self.object_user_1) 78 | -------------------------------------------------------------------------------- /ecsclient/common/configuration/configuration_properties.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class ConfigurationProperties(object): 15 | 16 | def __init__(self, connection): 17 | """ 18 | Initialize a new instance 19 | """ 20 | self.conn = connection 21 | 22 | def get_properties(self, category='ALL'): 23 | """ 24 | Gets the configuration properties for the specified category. If the 25 | category is not provided, then properties of all categories will 26 | be retrieved 27 | 28 | :param category: The category for which to retrieve configuration 29 | properties. If this is not provided it defaults to "ALL" 30 | 31 | Required role(s): 32 | 33 | SYSTEM_ADMIN 34 | SYSTEM_MONITOR 35 | 36 | Example JSON result from the API: 37 | 38 | { 39 | u'allProperties': { 40 | u'user_scope': u'GLOBAL' 41 | }, 42 | u'properties': { 43 | u'user_scope': u'GLOBAL' 44 | }, 45 | u'empty': False 46 | } 47 | 48 | """ 49 | log.info("Getting configurations in category '{0}'".format(category)) 50 | return self.conn.get( 51 | 'config/object/properties?category={0}'.format(category)) 52 | 53 | def get_properties_metadata(self): 54 | """ 55 | Gets the meta data for each of the configuration properties. 56 | Metadata includes the possible values for the property, a description 57 | of the property, whether a reboot is required when it is changed, etc. 58 | 59 | Required role(s): 60 | 61 | SYSTEM_ADMIN 62 | SYSTEM_MONITOR 63 | 64 | Example JSON result from the API: 65 | 66 | { 67 | u'metadata': { 68 | u'user_scope': { 69 | u'reconfigRequired': False, 70 | u'description': u'Declaresthescopeforusernameuniqueness', 71 | u'userMutable': False, 72 | u'minLen': 0, 73 | u'userConfigurable': False, 74 | u'defaultValue': None, 75 | u'controlNodeOnly': False, 76 | u'value': u'GLOBAL', 77 | u'allowedValues': [ 78 | u'NAMESPACE', 79 | u'GLOBAL' 80 | ], 81 | u'maxLen': 65534, 82 | u'defaultValueMetaData': u'GLOBAL', 83 | u'hidden': False, 84 | u'label': u'Userscope', 85 | u'type': u'string', 86 | u'rebootRequired': False, 87 | u'advanced': False 88 | } 89 | } 90 | } 91 | 92 | """ 93 | log.info("Getting metadata properties") 94 | return self.conn.get('config/object/properties/metadata') 95 | 96 | def set_properties(self): 97 | """ 98 | Sets the configuration properties for the system. 99 | 100 | Properties are: 101 | user-scope: defines whether user accounts should be treated as GLOBAL 102 | or NAMESPACE. 103 | 104 | In GLOBAL scope, users are global and are can be shared across 105 | namespaces. In this case, the default namespace associated with a user 106 | determines the namespace for object operations and there is no need to 107 | supply a namespace for an operation. 108 | 109 | If the user scope is NAMESPACE, a user is associated with a namespace, 110 | so there might be more than user with the same name, each associated 111 | with a different namespace. In NAMESPACE mode a namespace must be 112 | provided for every operation. Must be set before the first user is 113 | created. 114 | 115 | The default setting is GLOBAL. 116 | """ 117 | log.error("Configuration properties modification not supported") 118 | raise NotImplementedError 119 | -------------------------------------------------------------------------------- /ecsclient/common/exceptions.py: -------------------------------------------------------------------------------- 1 | import json 2 | from six.moves import urllib 3 | 4 | from ecsclient.common import error_codes 5 | 6 | 7 | class ECSClientException(Exception): 8 | """ 9 | This is a custom ECSClientException Exception class to better handle 10 | errors that ECSClientException can return in HTML format rather than JSON. 11 | It includes the original error message from ECS (ecs_message) and the 12 | HTTP status code returned (http_status_code). 13 | """ 14 | 15 | def __init__(self, message, ecs_code=None, ecs_retryable=None, ecs_description='', 16 | ecs_details='', http_scheme='', http_host='', http_port='', 17 | http_path='', http_query='', http_status=None, http_reason='', 18 | http_response_content='', http_response_headers=None): 19 | """ 20 | This is a custom ECSClientException class to handle API error responses 21 | """ 22 | super(ECSClientException, self).__init__(message) 23 | self.message = message 24 | self.ecs_code = ecs_code 25 | self.ecs_retryable = ecs_retryable 26 | self.ecs_description = ecs_description 27 | self.ecs_details = ecs_details 28 | self.http_scheme = http_scheme 29 | self.http_host = http_host 30 | self.http_port = http_port 31 | self.http_path = http_path 32 | self.http_query = http_query 33 | self.http_status = http_status 34 | self.http_reason = http_reason 35 | self.http_response_content = http_response_content 36 | self.http_response_headers = http_response_headers 37 | 38 | @classmethod 39 | def from_response(cls, resp, message=None): 40 | code = None 41 | retryable = None 42 | description = '' 43 | details = '' 44 | try: 45 | m = json.loads(resp.text) 46 | code = m.get('code', None) 47 | retryable = m.get('retryable', None) 48 | description = m.get('description', '') 49 | details = m.get('details', '') 50 | except BaseException: 51 | pass 52 | 53 | parsed_url = urllib.parse.urlparse(resp.request.url) 54 | message = message or details or description or error_codes.ERROR_CODES.get(code) or '%s %s' % ( 55 | resp.status_code, resp.reason) 56 | 57 | return cls(message, 58 | ecs_code=code, 59 | ecs_retryable=retryable, 60 | ecs_description=description, 61 | ecs_details=details, 62 | http_scheme=parsed_url.scheme, 63 | http_host=parsed_url.hostname, 64 | http_port=parsed_url.port, 65 | http_path=parsed_url.path, 66 | http_query=parsed_url.query, 67 | http_status=resp.status_code, 68 | http_reason=resp.reason, 69 | http_response_content=resp.text[:8192] or resp.content[:8192], 70 | http_response_headers=resp.headers) 71 | 72 | def __str__(self): 73 | a = "\n\nError Message: %s\n" % self.message 74 | if self.ecs_details not in a: 75 | a += 'ECS_details: %s\n' % self.ecs_details 76 | if self.ecs_description not in a: 77 | a += 'ECS_description: %s\n\n' % self.ecs_description 78 | b = '' 79 | if self.http_scheme: 80 | b += 'HTTP/S Request/Response Details:\n---------------------------------\nScheme: %s\n' % self.http_scheme 81 | if self.http_host: 82 | b += "Host: %s\n" % self.http_host 83 | if self.http_port: 84 | b += 'Port:%s\n' % self.http_port 85 | if self.http_path: 86 | b += 'Path: %s\n' % self.http_path 87 | if self.http_query: 88 | b += 'Query: %s\n' % self.http_query 89 | 90 | if self.http_status: 91 | if b: 92 | b += 'Response_Status: %s\n' % str(self.http_status) 93 | else: 94 | b = 'HTTP/S Response_Status: %s\n' % str(self.http_status) 95 | if self.http_reason: 96 | if b: 97 | b += 'Response_Reason: %s\n' % self.http_reason 98 | else: 99 | b = 'HTTP/S Response_Reason: %s\n' % self.http_reason 100 | if self.http_response_content: 101 | b += 'Response_content: %s' % self.http_response_content 102 | 103 | return b and a + b or a 104 | -------------------------------------------------------------------------------- /tests/functional/test_secret_key.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from ecsclient.common.exceptions import ECSClientException 5 | from tests import functional 6 | 7 | 8 | class TestSecretKey(functional.BaseTestCase): 9 | def __init__(self, *args, **kwargs): 10 | super(TestSecretKey, self).__init__(*args, **kwargs) 11 | self.namespace_1 = "functional-tests-namespace-%s" % int(time.time()) 12 | self.object_user_1 = "functional-tests-objectuser-%s" % int(time.time()) 13 | self.object_user_current = None 14 | self.secret_key_1 = "E3GooHbvQXSxHplji1aPEGZilIEWYfveJQtFIrAF" 15 | self.current_user_created = False 16 | 17 | def setUp(self): 18 | super(TestSecretKey, self).setUp() 19 | self.client.namespace.create(self.namespace_1) 20 | self.client.object_user.create(self.object_user_1, self.namespace_1) 21 | self.client.secret_key.create(user_id=self.object_user_1, 22 | namespace=self.namespace_1, 23 | secret_key=self.secret_key_1) 24 | r = self.client.user_info.whoami() 25 | self.object_user_current = r['common_name'] 26 | try: 27 | self.client.object_user.create(self.object_user_current, self.namespace_1) 28 | self.current_user_created = True 29 | except ECSClientException: 30 | pass 31 | self.client.secret_key.create() 32 | 33 | def tearDown(self): 34 | super(TestSecretKey, self).tearDown() 35 | self.client.object_user.delete(self.object_user_1) 36 | if self.current_user_created: 37 | self.client.object_user.delete(self.object_user_current) 38 | self.client.namespace.delete(self.namespace_1) 39 | 40 | def test_secret_key_get_authenticated_user(self): 41 | response = self.client.secret_key.get() 42 | self.assertValidSchema(response, schemas.SECRET_KEYS) 43 | 44 | def test_secret_key_get_by_user(self): 45 | response = self.client.secret_key.get(user_id=self.object_user_1, 46 | namespace=self.namespace_1) 47 | self.assertValidSchema(response, schemas.SECRET_KEYS) 48 | self.assertEqual(response['secret_key_1'], self.secret_key_1) 49 | 50 | def test_secret_key_create_authenticated_user(self): 51 | response = self.client.secret_key.create() 52 | self.assertValidSchema(response, schemas.SECRET_KEY) 53 | 54 | def test_secret_key_create_for_user(self): 55 | secret_key = 'zDR3nIO9ywbdpabxKSbnB3NKGIclBSEpyCkh0KJB' 56 | response = self.client.secret_key.create(user_id=self.object_user_1, 57 | namespace=self.namespace_1, 58 | expiry_time=60, 59 | secret_key=secret_key) 60 | self.assertValidSchema(response, schemas.SECRET_KEY) 61 | self.assertEqual(response['secret_key'], secret_key) 62 | 63 | def test_secret_key_delete_authenticated_user(self): 64 | response = self.client.secret_key.get() 65 | self.assertNotEqual(response['secret_key_1'] or response['secret_key_2'], "") 66 | 67 | self.client.secret_key.delete() 68 | 69 | response = self.client.secret_key.get() 70 | self.assertEqual(response['secret_key_1'], "") 71 | self.assertEqual(response['secret_key_2'], "") 72 | 73 | def test_secret_key_delete_for_user(self): 74 | response = self.client.secret_key.get(user_id=self.object_user_1, namespace=self.namespace_1) 75 | self.assertNotEqual(response['secret_key_1'] or response['secret_key_2'], "") 76 | 77 | self.client.secret_key.delete(user_id=self.object_user_1, namespace=self.namespace_1) 78 | 79 | response = self.client.secret_key.get(user_id=self.object_user_1, namespace=self.namespace_1) 80 | self.assertEqual(response['secret_key_1'], "") 81 | self.assertEqual(response['secret_key_2'], "") 82 | 83 | def test_secret_key_replace(self): 84 | secret_key = 'A3GooHbvQXSxHplji1aPEGZilIEWYfveJQtFIrAF' 85 | 86 | self.client.secret_key.delete(user_id=self.object_user_1, namespace=self.namespace_1) 87 | 88 | response = self.client.secret_key.get(user_id=self.object_user_1, namespace=self.namespace_1) 89 | self.assertEqual(response['secret_key_1'], "") 90 | self.assertEqual(response['secret_key_2'], "") 91 | 92 | self.client.secret_key.create(user_id=self.object_user_1, 93 | namespace=self.namespace_1, 94 | secret_key=secret_key) 95 | 96 | response = self.client.secret_key.get(user_id=self.object_user_1, namespace=self.namespace_1) 97 | self.assertEqual(response['secret_key_1'], secret_key) 98 | self.assertEqual(response['secret_key_2'], "") 99 | -------------------------------------------------------------------------------- /tests/unit/test_token_request.py: -------------------------------------------------------------------------------- 1 | import testtools 2 | from mock import mock 3 | from requests_mock.contrib import fixture 4 | from mock import MagicMock 5 | from mock import mock_open 6 | from mock import patch 7 | from six.moves import http_client 8 | from ecsclient.common.token_request import TokenRequest 9 | from ecsclient.common.exceptions import ECSClientException 10 | 11 | 12 | class TestTokenRequest(testtools.TestCase): 13 | 14 | def setUp(self): 15 | super(TestTokenRequest, self).setUp() 16 | self.token_file_contents = '123TOKEN' 17 | self.response = MagicMock() 18 | self.session_get = 'ecsclient.common.token_request.requests.Session.get' 19 | 20 | self.token_request = TokenRequest(username='someone', 21 | password='password', 22 | ecs_endpoint='https://127.0.0.1:4443', 23 | token_endpoint='https://127.0.0.1:4443/login', 24 | verify_ssl=False, 25 | token_path='/tmp/ecstoken.tkn', 26 | request_timeout=5.0, 27 | cache_token=True) 28 | self.requests_mock = self.useFixture(fixture.Fixture()) 29 | 30 | def test_should_get_existing_token(self): 31 | with patch('os.path.isfile', return_value=True),\ 32 | patch('six.moves.builtins.open', mock_open(read_data='123TOKEN'), 33 | create=True): 34 | self.assertEqual(self.token_request._get_existing_token(), 35 | self.token_file_contents) 36 | 37 | def test_should_not_get_existing_token(self): 38 | with patch('os.path.isfile', return_value=False): 39 | self.assertEqual(self.token_request._get_existing_token(), None) 40 | 41 | def test_get_new_token_should_throw_ecsclientexception_500(self): 42 | self.requests_mock.register_uri('GET', 'https://127.0.0.1:4443/login', 43 | status_code=http_client.INTERNAL_SERVER_ERROR) 44 | 45 | with super(testtools.TestCase, self).assertRaises(ECSClientException) as error: 46 | self.token_request.get_new_token() 47 | 48 | exception = error.exception 49 | self.assertEqual(exception.http_status, http_client.INTERNAL_SERVER_ERROR) 50 | 51 | def test_get_new_token_should_throw_ecsclientexception_401(self): 52 | self.requests_mock.register_uri('GET', 'https://127.0.0.1:4443/login', 53 | status_code=http_client.UNAUTHORIZED) 54 | 55 | with super(testtools.TestCase, self).assertRaises(ECSClientException) as error: 56 | self.token_request.get_new_token() 57 | 58 | exception = error.exception 59 | self.assertEqual(exception.http_status, http_client.UNAUTHORIZED) 60 | 61 | @mock.patch('ecsclient.common.token_request.os.path.isdir') 62 | def test_get_new_token_cache_invalid_token_path(self, mock_isdir): 63 | self.requests_mock.register_uri('GET', 'https://127.0.0.1:4443/login', 64 | headers={'X-SDS-AUTH-TOKEN': 'NEW-TOKEN-123'}) 65 | mock_isdir.side_effect = lambda dir_: dir_ != '/foo/bar' 66 | self.token_request.cache_token = True 67 | self.token_request.token_path = '/foo/bar/token.txt' 68 | 69 | with super(testtools.TestCase, self).assertRaises(ECSClientException) as error: 70 | self.token_request.get_new_token() 71 | 72 | exception = error.exception 73 | self.assertEqual(exception.message, "Token directory not found") 74 | 75 | @mock.patch('ecsclient.common.token_request.TokenRequest.get_new_token') 76 | @mock.patch('ecsclient.common.token_request.TokenRequest._get_existing_token') 77 | def test_token_validation_401(self, mock_get_existing_token, mock_get_new_token): 78 | self.requests_mock.register_uri('GET', 'https://127.0.0.1:4443/user/whoami', 79 | status_code=http_client.UNAUTHORIZED) 80 | mock_get_new_token.return_value = 'NEW-TOKEN-123' 81 | mock_get_existing_token.return_value = 'EXISTING-TOKEN-123' 82 | 83 | token = self.token_request.get_token() 84 | 85 | self.assertEqual(token, 'NEW-TOKEN-123') 86 | 87 | @mock.patch('ecsclient.common.token_request.TokenRequest._get_existing_token') 88 | def test_token_validation_500(self, mock_get_existing_token): 89 | self.requests_mock.register_uri('GET', 'https://127.0.0.1:4443/user/whoami', 90 | status_code=http_client.INTERNAL_SERVER_ERROR) 91 | mock_get_existing_token.return_value = 'EXISTING-TOKEN-123' 92 | 93 | with super(testtools.TestCase, self).assertRaises(ECSClientException) as error: 94 | self.token_request.get_token() 95 | 96 | exception = error.exception 97 | self.assertEqual(exception.message, "Token validation error (Code: 500)") 98 | self.assertEqual(exception.http_status, http_client.INTERNAL_SERVER_ERROR) 99 | -------------------------------------------------------------------------------- /ecsclient/common/user_management/password_group.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import logging 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | class PasswordGroup(object): 8 | def __init__(self, connection): 9 | """ 10 | Initialize a new instance 11 | """ 12 | self.conn = connection 13 | 14 | def get(self, user_id, namespace=None): 15 | """ 16 | Gets all Swift user groups for a specified user identifier. 17 | If namespace is provided then returns only groups for the specified namespace. 18 | 19 | Required role(s): 20 | 21 | SYSTEM_ADMIN 22 | SYSTEM_MONITOR 23 | NAMESPACE_ADMIN 24 | 25 | Example JSON result from the API: 26 | 27 | { 28 | "groups_list": [ 29 | "admin" 30 | ] 31 | } 32 | 33 | :param user_id: Username for which group names should be returned. 34 | :param namespace: Namespace limiting returned groups (optional). 35 | """ 36 | msg = "Getting Swift groups for user '{}'".format(user_id) 37 | url = 'object/user-password/{}'.format(user_id) 38 | 39 | if namespace: 40 | url += '/{}'.format(namespace) 41 | msg += " in namespace '{}'".format(namespace) 42 | 43 | log.info(msg) 44 | return self.conn.get(url=url) 45 | 46 | def create(self, user_id, password, groups_list, namespace=None): 47 | """ 48 | Creates password and group for a specific user. 49 | 50 | Required role(s): 51 | 52 | SYSTEM_ADMIN 53 | NAMESPACE_ADMIN 54 | 55 | There is no response body for this call 56 | 57 | Expect: HTTP/1.1 200 OK 58 | 59 | :param user_id: Valid user identifier to create a key for. If not provided, the authenticated 60 | user will be used instead. 61 | :param namespace: Namespace for the user if the user belongs to a namespace. 62 | :param password: Swift password associated with this user. 63 | :param groups_list: List of Swift groups with which to associate this user. If user is a member 64 | of the "admin" group, user will be able to perform all container operations. 65 | If a member of any other group, authorization will depend on the access that is set on the container. 66 | """ 67 | 68 | url = 'object/user-password/{}'.format(user_id) 69 | msg = "Creating Swift password for user '{}' in namespace '{}'".format(user_id, namespace) 70 | 71 | payload = {'namespace': namespace, 72 | 'password': password, 73 | 'groups_list': groups_list} 74 | 75 | log.info(msg) 76 | return self.conn.put(url, json_payload=payload) 77 | 78 | def update(self, user_id, password, groups_list, namespace=None): 79 | """ 80 | Updates password and group information for a specific user identifier. 81 | 82 | Required role(s): 83 | 84 | SYSTEM_ADMIN 85 | NAMESPACE_ADMIN 86 | 87 | There is no response body for this call 88 | 89 | Expect: HTTP/1.1 200 OK 90 | 91 | :param user_id: Valid user identifier to create a key for. If not provided, 92 | the authenticated user will be used instead. 93 | :param namespace: Namespace for the user if the user belongs to a namespace. 94 | :param password: Swift password associated with this user. 95 | :param groups_list: List of Swift groups with which to associate this user. If user is a member 96 | of the "admin" group, user will be able to perform all container operations. 97 | If a member of any other group, authorization will depend on the access that is set on the container. 98 | """ 99 | 100 | url = 'object/user-password/{}'.format(user_id) 101 | msg = "Updating Swift password for user '{}' in namespace '{}'".format(user_id, namespace) 102 | 103 | payload = {'namespace': namespace, 104 | 'password': password, 105 | 'groups_list': groups_list} 106 | 107 | log.info(msg) 108 | return self.conn.post(url, json_payload=payload) 109 | 110 | def delete(self, user_id, namespace=None): 111 | """ 112 | Deletes password group for a specified user. 113 | 114 | Required role(s): 115 | 116 | SYSTEM_ADMIN 117 | NAMESPACE_ADMIN 118 | 119 | Example JSON result from the API: 120 | 121 | There is no response body for this call 122 | 123 | Expect: HTTP/1.1 204 No Response 124 | 125 | :param user_id: Valid user identifier to get delete the keys from. 126 | :param namespace: Namespace for the user if the user belongs to a namespace. 127 | """ 128 | 129 | url = 'object/user-password/{}/deactivate'.format(user_id) 130 | msg = "Deleting Swift password for user '{}' in namespace '{}'".format(user_id, namespace) 131 | 132 | payload = {'namespace': namespace} 133 | 134 | log.info(msg) 135 | return self.conn.post(url, json_payload=payload) 136 | -------------------------------------------------------------------------------- /tests/functional/test_replication_group.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from ecsclient import schemas 4 | from ecsclient.common.exceptions import ECSClientException 5 | from tests import functional 6 | 7 | 8 | class TestReplicationGroup(functional.BaseTestCase): 9 | 10 | def setUp(self): 11 | super(TestReplicationGroup, self).setUp() 12 | # Initialize the resource names 13 | self.storage_pool_1 = "functional-tests-storagepool-%s" % int(time.time()) 14 | self.replication_group_1 = "functional-tests-replicationgroup-%s" % int(time.time()) 15 | self.replication_group_1_id = "placeholder" 16 | self.replication_group_2 = self.replication_group_1 + '_second' 17 | self.replication_group_2_id = "placeholder" 18 | 19 | # Get the local VDC 20 | r = self.client.vdc.get_local() 21 | self.vdc_1_id = r['id'] 22 | 23 | # Create a Storage Pool 24 | r = self.client.storage_pool.create(self.storage_pool_1) 25 | self.storage_pool_1_id = r['id'] 26 | 27 | # Create a Replication Group 28 | zone_mappings = [( 29 | self.vdc_1_id, 30 | self.storage_pool_1_id 31 | )] 32 | r = self.client.replication_group.create(self.replication_group_1, zone_mappings) 33 | self.replication_group_1_id = r['id'] 34 | 35 | def tearDown(self): 36 | super(TestReplicationGroup, self).tearDown() 37 | 38 | try: 39 | self.client.storage_pool.delete(self.storage_pool_1_id) 40 | except ECSClientException: 41 | pass 42 | 43 | for replication_group_id in [self.replication_group_1_id, 44 | self.replication_group_2_id]: 45 | try: 46 | self.client.replication_group.delete(replication_group_id) 47 | except ECSClientException: 48 | pass 49 | 50 | def test_replication_group_list(self): 51 | response = self.client.replication_group.list() 52 | self.assertValidSchema(response, schemas.REPLICATION_GROUPS) 53 | 54 | def test_replication_group_get_one(self): 55 | response = self.client.replication_group.get(self.replication_group_1_id) 56 | self.assertValidSchema(response, schemas.REPLICATION_GROUP) 57 | self.assertEqual(response['id'], self.replication_group_1_id) 58 | 59 | def test_replication_group_update(self): 60 | new_name = self.replication_group_1 + '_updated' 61 | response = self.client.replication_group.get(self.replication_group_1_id) 62 | self.assertNotEqual(response['name'], new_name) 63 | self.assertNotEqual(response['description'], new_name + ' description') 64 | self.assertTrue(response['isAllowAllNamespaces']) 65 | self.assertFalse(response['enable_rebalancing']) 66 | 67 | self.client.replication_group.update(self.replication_group_1_id, 68 | name=new_name, 69 | description=new_name + ' description', 70 | allow_all_namespaces=False, 71 | enable_rebalancing=True) 72 | 73 | response = self.client.replication_group.get(self.replication_group_1_id) 74 | self.assertEqual(response['name'], new_name) 75 | self.assertEqual(response['description'], new_name + ' description') 76 | self.assertFalse(response['isAllowAllNamespaces']) 77 | self.assertTrue(response['enable_rebalancing']) 78 | 79 | def test_replication_group_create(self): 80 | zone_mappings = [( 81 | self.vdc_1_id, 82 | self.storage_pool_1_id 83 | )] 84 | response = self.client.replication_group.create(self.replication_group_2, 85 | zone_mappings, 86 | description=self.replication_group_2 + " description", 87 | allow_all_namespaces=False, 88 | is_full_rep=False, 89 | enable_rebalancing=True) 90 | self.assertValidSchema(response, schemas.REPLICATION_GROUP) 91 | self.assertEqual(response['name'], self.replication_group_2) 92 | self.assertEqual(response['description'], self.replication_group_2 + " description") 93 | self.assertFalse(response['isAllowAllNamespaces']) 94 | self.assertFalse(response['isFullRep']) 95 | self.assertTrue(response['enable_rebalancing']) 96 | self.assertEqual(response['varrayMappings'][0]['name'], self.vdc_1_id) 97 | self.assertEqual(response['varrayMappings'][0]['value'], self.storage_pool_1_id) 98 | 99 | def test_replication_group_delete(self): 100 | self.client.replication_group.delete(self.replication_group_1_id) 101 | f = self.client.replication_group.get 102 | self.assertRaises(ECSClientException, f, self.replication_group_1_id) 103 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # python-ecsclient documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Feb 20 14:58:42 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix(es) of source filenames. 39 | # You can specify multiple suffix as a list of string: 40 | # 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = '.rst' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'python-ecsclient' 49 | copyright = u'2017, Dell EMC' 50 | author = u'Dell EMC' 51 | 52 | # The version info for the project you're documenting, acts as replacement for 53 | # |version| and |release|, also used in various other places throughout the 54 | # built documents. 55 | # 56 | # The short X.Y version. 57 | version = u'1' 58 | # The full version, including alpha/beta/rc tags. 59 | release = u'1' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This patterns also effect to html_static_path and html_extra_path 71 | exclude_patterns = [] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = 'sphinx' 75 | 76 | # If true, `todo` and `todoList` produce output, else they produce nothing. 77 | todo_include_todos = False 78 | 79 | 80 | # -- Options for HTML output ---------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'alabaster' 86 | 87 | # Theme options are theme-specific and customize the look and feel of a theme 88 | # further. For a list of options available for each theme, see the 89 | # documentation. 90 | # 91 | # html_theme_options = {} 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | html_static_path = ['_static'] 97 | 98 | 99 | # -- Options for HTMLHelp output ------------------------------------------ 100 | 101 | # Output file base name for HTML help builder. 102 | htmlhelp_basename = 'python-ecsclientdoc' 103 | 104 | 105 | # -- Options for LaTeX output --------------------------------------------- 106 | 107 | latex_elements = { 108 | # The paper size ('letterpaper' or 'a4paper'). 109 | # 110 | # 'papersize': 'letterpaper', 111 | 112 | # The font size ('10pt', '11pt' or '12pt'). 113 | # 114 | # 'pointsize': '10pt', 115 | 116 | # Additional stuff for the LaTeX preamble. 117 | # 118 | # 'preamble': '', 119 | 120 | # Latex figure (float) alignment 121 | # 122 | # 'figure_align': 'htbp', 123 | } 124 | 125 | # Grouping the document tree into LaTeX files. List of tuples 126 | # (source start file, target name, title, 127 | # author, documentclass [howto, manual, or own class]). 128 | latex_documents = [ 129 | (master_doc, 'python-ecsclient.tex', u'python-ecsclient Documentation', 130 | u'Dell EMC', 'manual'), 131 | ] 132 | 133 | 134 | # -- Options for manual page output --------------------------------------- 135 | 136 | # One entry per manual page. List of tuples 137 | # (source start file, name, description, authors, manual section). 138 | man_pages = [ 139 | (master_doc, 'python-ecsclient', u'python-ecsclient Documentation', 140 | [author], 1) 141 | ] 142 | 143 | 144 | # -- Options for Texinfo output ------------------------------------------- 145 | 146 | # Grouping the document tree into Texinfo files. List of tuples 147 | # (source start file, target name, title, author, 148 | # dir menu entry, description, category) 149 | texinfo_documents = [ 150 | (master_doc, 'python-ecsclient', u'python-ecsclient Documentation', 151 | author, 'python-ecsclient', 'One line description of project.', 152 | 'Miscellaneous'), 153 | ] 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /ecsclient/common/user_management/management_user.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class ManagementUser(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def list(self): 14 | """ 15 | Gets all configured local management users. 16 | 17 | Required role(s): 18 | 19 | SYSTEM_ADMIN 20 | SYSTEM_MONITOR 21 | 22 | Example JSON result from the API: 23 | 24 | { 25 | u'mgmt_user_info': [ 26 | { 27 | u'isSystemMonitor': False, 28 | u'isSecurityAdmin': False, 29 | u'userId': u'someone@internal', 30 | u'isSystemAdmin': True 31 | }, 32 | { 33 | u'isSystemMonitor': False, 34 | u'isSecurityAdmin': False, 35 | u'userId': u'root', 36 | u'isSystemAdmin': True 37 | } 38 | ] 39 | } 40 | """ 41 | log.info('Listing all local management users') 42 | return self.conn.get(url='vdc/users') 43 | 44 | def get(self, user_id): 45 | """ 46 | Gets details for the specified local management user. 47 | 48 | Required role(s): 49 | 50 | SYSTEM_ADMIN 51 | SYSTEM_MONITOR 52 | 53 | Example JSON result from the API: 54 | 55 | { 56 | u'isSystemMonitor': False, 57 | u'userId': u'admin', 58 | u'isSystemAdmin': True 59 | u'isSecurityAdmin': True 60 | } 61 | 62 | :param user_id: User identifier for which local user information needs to 63 | be retrieved 64 | """ 65 | log.info("Getting local management user '{}'".format(user_id)) 66 | return self.conn.get(url='vdc/users/{}'.format(user_id)) 67 | 68 | def delete(self, user_id): 69 | """ 70 | Deletes local management user information for the specified user 71 | identifier. 72 | 73 | Required role(s): 74 | 75 | SYSTEM_ADMIN 76 | 77 | There is no response body for this call 78 | 79 | Expect: HTTP/1.1 200 OK 80 | 81 | :param user_id: User identifier for which local user information needs to 82 | be deleted. 83 | """ 84 | log.info("Deleting local management user '{}'".format(user_id)) 85 | return self.conn.post(url='vdc/users/{}/deactivate'.format(user_id)) 86 | 87 | def create(self, user_id, password, is_system_admin=False, 88 | is_system_monitor=False, is_security_admin=False): 89 | """ 90 | Creates local users for the VDC. These users can be assigned to 91 | VDC-wide management roles and are not associated with a namespace. 92 | User account can be assigned to the System Admin role by setting the 93 | isSystemAdmin flag in the request payload. 94 | 95 | Required role(s): 96 | 97 | SYSTEM_ADMIN 98 | 99 | :param user_id: Management user id, ex: sampleuser 100 | :param password: Password for the management user 101 | :param is_system_admin: If set to True, assigns the management user to 102 | the System Admin role. Default: False 103 | :param is_system_monitor: If set to True, assigns the management user 104 | to the System Monitor role. Default: False 105 | :param is_security_admin: If set to True, assigns the management user 106 | to the Security Admin role. Default: False 107 | """ 108 | payload = { 109 | "userId": user_id, 110 | "password": password, 111 | "isSystemAdmin": is_system_admin, 112 | "isSystemMonitor": is_system_monitor, 113 | "isSecurityAdmin": is_security_admin 114 | } 115 | 116 | log.info("Creating local management user '{}'".format(user_id)) 117 | return self.conn.post(url='vdc/users', json_payload=payload) 118 | 119 | def update(self, user_id, password, is_system_admin=False, 120 | is_system_monitor=False, is_security_admin=False): 121 | """ 122 | Updates user details for the specified local management user. 123 | 124 | Required role(s): 125 | 126 | SYSTEM_ADMIN 127 | 128 | There is no response body for this call 129 | 130 | Expect: HTTP/1.1 200 OK 131 | 132 | :param user_id: User identifier for which local user information needs to 133 | be updated. 134 | :param password: Password for the management user 135 | :param is_system_admin: If set to True, assigns the management user to 136 | the System Admin role. Default: False 137 | :param is_system_monitor: If set to True, assigns the management user 138 | to the System Monitor role. Default: False 139 | :param is_security_admin: If set to True, assigns the management user 140 | to the Security Admin role. Default: False 141 | """ 142 | payload = { 143 | "password": password, 144 | "isSystemAdmin": is_system_admin, 145 | "isSystemMonitor": is_system_monitor, 146 | "isSecurityAdmin": is_security_admin 147 | } 148 | 149 | log.info("Updating local management user '{}'".format(user_id)) 150 | 151 | return self.conn.put( 152 | url='vdc/users/{}'.format(user_id), json_payload=payload) 153 | -------------------------------------------------------------------------------- /ecsclient/common/provisioning/storage_pool.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class StoragePool(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get(self, storage_pool_id): 14 | """ 15 | Gets the details for the specified storage pool. 16 | 17 | Required role(s): 18 | 19 | SYSTEM_ADMIN 20 | SYSTEM_MONITOR 21 | 22 | Example JSON result from the API: 23 | 24 | { 25 | 'isProtected': False, 26 | 'isColdStorageEnabled': False, 27 | 'id': 'urn: storageos: VirtualArray: 28 | 3c4e8cca-2e3d-4f8d-b183-1c69ce2d5b37', 29 | 'name': 'storagepool1' 30 | } 31 | 32 | :param storage_pool_id: Storage pool identifier to be retrieved 33 | """ 34 | log.info("Getting storage pool '{}'".format(storage_pool_id)) 35 | return self.conn.get('vdc/data-services/varrays/{}'.format(storage_pool_id)) 36 | 37 | def list(self, vdc_id=None): 38 | """ 39 | Gets a list of storage pools from the local VDC. 40 | 41 | Required role(s): 42 | 43 | SYSTEM_ADMIN 44 | SYSTEM_MONITOR 45 | 46 | Example JSON result from the API: 47 | 48 | { 49 | 'varray': [ 50 | { 51 | 'isProtected': False, 52 | 'isColdStorageEnabled': False, 53 | 'id': 'urn: storageos: VirtualArray: 54 | 3c4e8cca-2e3d-4f8d-b183-1c69ce2d5b37', 55 | 'name': 'storagepool1' 56 | }, 57 | { 58 | 'isProtected': True, 59 | 'isColdStorageEnabled': False, 60 | 'id': 'urn: storageos: VirtualArray: 61 | c7fc54dc-6616-4b7e-a86c-0210ef9a8804', 62 | 'name': 'storagepool2' 63 | } 64 | ] 65 | } 66 | 67 | :param vdc_id: Virtual data center identifier for which list of storage pool is to be retrieved 68 | """ 69 | msg = "Listing storage pools" 70 | params = None 71 | if vdc_id: 72 | params = {'vdc-id': vdc_id} 73 | msg += " for VDC '{}'".format(vdc_id) 74 | log.info(msg) 75 | return self.conn.get('vdc/data-services/varrays', params=params) 76 | 77 | def create(self, name, description=None, is_protected=False, is_cold_storage_enabled=False): 78 | """ 79 | Create a storage pool with the specified details. 80 | 81 | Required role(s): 82 | 83 | SYSTEM_ADMIN 84 | 85 | Example JSON result from the API: 86 | 87 | { 88 | 'id': 'urn:storageos:VirtualArray:dd751e72-142-598-b4f833e93b61', 89 | 'name': 'storage_pool', 90 | 'isProtected': False, 91 | 'isColdStorageEnabled': False 92 | } 93 | 94 | :param name: Storage pool name 95 | :param description: Storage pool description 96 | :param is_protected: Set to True to enable storage pool protection, False otherwise 97 | :param is_cold_storage_enabled: Set to True to enable cold storage, False otherwise 98 | """ 99 | if not description: 100 | description = name 101 | 102 | payload = { 103 | "name": name, 104 | "description": description, 105 | "isProtected": is_protected, 106 | "isColdStorageEnabled": is_cold_storage_enabled 107 | } 108 | log.info("Creating storage pool '{}'".format(name)) 109 | return self.conn.post('vdc/data-services/varrays', json_payload=payload) 110 | 111 | def update(self, storage_pool_id, name=None, is_protected=None): 112 | """ 113 | Updates storage pool for the specified identifier. 114 | 115 | Required role(s): 116 | 117 | SYSTEM_ADMIN 118 | 119 | Example JSON result from the API: 120 | 121 | { 122 | 'id': 'urn:storageos:VirtualArray:82cf257b-0782-433c-92ca-2ee6161b917e', 123 | 'name': 'CommodityvPool', 124 | 'description': 'desc', 125 | 'isProtected': True, 126 | 'isColdStorageEnabled': False 127 | } 128 | 129 | :param storage_pool_id: Storage pool identifier to be updated 130 | :param name: Name of virtual array to be updated 131 | :param is_protected: Set to True to enable storage pool protection, False otherwise 132 | """ 133 | payload = { 134 | "name": name, 135 | "isProtected": is_protected 136 | } 137 | # FIXME: API throws error if 'isColdStorageEnabled' and 'description' fields are sent 138 | log.info("Updating storage pool ID '{}'".format(storage_pool_id)) 139 | return self.conn.put('vdc/data-services/varrays/{}'.format(storage_pool_id), 140 | json_payload=payload) 141 | 142 | def delete(self, storage_pool_id): 143 | """ 144 | Deletes the storage pool for the specified identifier. 145 | 146 | Required role(s): 147 | 148 | SYSTEM_ADMIN 149 | 150 | There is no response body for this call 151 | 152 | Expect: HTTP/1.1 200 OK 153 | 154 | :param storage_pool_id: storage pool identifier to be deleted 155 | """ 156 | log.info("Deleting storage pool ID '{}'".format(storage_pool_id)) 157 | return self.conn.delete('vdc/data-services/varrays/{}'.format(storage_pool_id)) 158 | -------------------------------------------------------------------------------- /tests/unit/test_ecsclient.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import mock 4 | 5 | from ecsclient import baseclient 6 | from ecsclient import v2, v3, v4 7 | from ecsclient.client import Client 8 | from ecsclient.common.exceptions import ECSClientException 9 | 10 | 11 | class TestEcsClient(unittest.TestCase): 12 | def test_verify_attributes(self): 13 | c = baseclient.Client(username='someone', 14 | password='password', 15 | ecs_endpoint='http://127.0.0.1:4443', 16 | token_endpoint='http://127.0.0.1:4443/login') 17 | attributes = ['token_endpoint', 18 | 'username', 19 | 'password', 20 | 'token', 21 | 'ecs_endpoint', 22 | 'verify_ssl', 23 | 'token_path', 24 | 'request_timeout', 25 | 'cache_token', 26 | '_session', 27 | '_token_request', 28 | 'authentication'] 29 | for attr in attributes: 30 | self.assertTrue(hasattr(c, attr)) 31 | 32 | def test_client_without_version(self): 33 | with self.assertRaises(RuntimeError) as error: 34 | Client(username='user', 35 | password='password', 36 | ecs_endpoint='https://192.168.1.10', 37 | token_endpoint='https://192.168.10/login') 38 | exception = error.exception 39 | self.assertIn('Please provide the API version', str(exception)) 40 | 41 | def test_client_unsupported_version(self): 42 | with self.assertRaises(RuntimeError) as error: 43 | Client(version='10', 44 | username='user', 45 | password='password', 46 | ecs_endpoint='https://192.168.1.10', 47 | token_endpoint='https://192.168.10/login') 48 | exception = error.exception 49 | self.assertEqual("No client available for version '10'", str(exception)) 50 | 51 | def test_client_without_ecs_endpoint(self): 52 | with self.assertRaises(ECSClientException) as error: 53 | Client(version='3', 54 | username='user', 55 | password='password', 56 | token_endpoint='https://192.168.10/login') 57 | exception = error.exception.message 58 | self.assertEqual("Missing 'ecs_endpoint'", str(exception)) 59 | 60 | @mock.patch('ecsclient.baseclient.os.path.isfile') 61 | def test_client_without_token_endpoint(self, mock_isfile): 62 | mock_isfile.return_value = False 63 | with self.assertRaises(ECSClientException) as error: 64 | Client(version='3', 65 | username='user', 66 | password='password', 67 | ecs_endpoint='https://192.168.1.10') 68 | exception = error.exception.message 69 | mock_isfile.assert_called_with('/tmp/ecsclient.tkn') 70 | self.assertEqual("'token_endpoint' not provided and missing 'token'|'token_path'", str(exception)) 71 | 72 | def test_client_without_credentials(self): 73 | with self.assertRaises(ECSClientException) as error: 74 | Client(version='3', 75 | ecs_endpoint='https://192.168.1.10', 76 | token_endpoint='https://192.168.10/login') 77 | exception = error.exception.message 78 | self.assertEqual("'token_endpoint' provided but missing ('username','password')", str(exception)) 79 | 80 | def test_client_v4_class(self): 81 | c = Client(version='4', 82 | username='user', 83 | password='password', 84 | ecs_endpoint='https://192.168.1.10', 85 | token_endpoint='https://192.168.10/login') 86 | self.assertIsInstance(c, v4.client.Client, 'Instance is not a v4 client class') 87 | 88 | def test_client_v3_class(self): 89 | c = Client(version='3', 90 | username='user', 91 | password='password', 92 | ecs_endpoint='https://192.168.1.10', 93 | token_endpoint='https://192.168.10/login') 94 | self.assertIsInstance(c, v3.client.Client, 'Instance is not a v3 client class') 95 | 96 | def test_client_v2_class(self): 97 | c = Client(version='2', 98 | username='user', 99 | password='password', 100 | ecs_endpoint='https://192.168.1.10', 101 | token_endpoint='https://192.168.10/login') 102 | self.assertIsInstance(c, v2.client.Client, 'Instance is not a v2 client class') 103 | 104 | def test_client_init_with_credentials(self): 105 | c = Client(version='3', 106 | username='user', 107 | password='password', 108 | token_endpoint='https://192.168.10/login', 109 | ecs_endpoint='https://192.168.1.10') 110 | self.assertTrue(hasattr(c, 'username')) 111 | self.assertTrue(hasattr(c, 'password')) 112 | self.assertTrue(hasattr(c, 'token_endpoint')) 113 | 114 | def test_client_init_with_token(self): 115 | c = Client(version='3', 116 | token='1234567890', 117 | ecs_endpoint='https://192.168.1.10') 118 | self.assertTrue(hasattr(c, 'token')) 119 | 120 | @mock.patch('ecsclient.baseclient.os.path.isfile') 121 | def test_client_init_with_token_path(self, mock_isfile): 122 | mock_isfile.return_value = True 123 | c = Client(version='3', 124 | token_path='/tmp/mytoken.tkn', 125 | ecs_endpoint='https://192.168.1.10') 126 | self.assertTrue(hasattr(c, 'token_path')) 127 | mock_isfile.assert_called_with('/tmp/mytoken.tkn') 128 | -------------------------------------------------------------------------------- /ecsclient/v3/configuration/snmp.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Snmp(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get_snmp_agent(self): 14 | """ 15 | Get SNMP Agent configuration including targets. 16 | 17 | Required role(s): 18 | 19 | SYSTEM_ADMIN 20 | SYSTEM_MONITOR 21 | 22 | Example JSON result from the API: 23 | """ 24 | # TODO: Add example JSON response 25 | log.info("Fetching SNMP Agent configuration") 26 | return self.conn.get('vdc/snmp/config') 27 | 28 | def update_snmp_agent(self, engine_id): 29 | """ 30 | Sets SNMP agent configuration 31 | 32 | Required role(s): 33 | 34 | SYSTEM_ADMIN 35 | 36 | There is no response body for this call 37 | 38 | Expect: HTTP/1.1 200 OK 39 | 40 | :param engine_id: Fully qualified domain name or IP and port 41 | """ 42 | payload = { 43 | "engineID": engine_id 44 | } 45 | log.info("Setting SNMP agent configuration with Engine ID '{}'".format(engine_id)) 46 | return self.conn.put('vdc/snmp/config', json_payload=payload) 47 | 48 | def get_snmp_target(self, target_id): 49 | """ 50 | Get SNMP target info. 51 | 52 | Required role(s): 53 | 54 | SYSTEM_ADMIN 55 | SYSTEM_MONITOR 56 | 57 | Example JSON result from the API: 58 | {} 59 | 60 | :param target_id: SNMP target ID 61 | """ 62 | # TODO: Add example JSON response 63 | log.info("Getting SNMP target information with ID '{}'".format(target_id)) 64 | return self.conn.get('vdc/snmp/config/target/{}'.format(target_id)) 65 | 66 | def create_snmp_target(self, server, port, version, community, username, auth_protocol, 67 | auth_passphrase, privacy_protocol, privacy_passphrase): 68 | """ 69 | Adds SNMP Target. 70 | 71 | Required role(s): 72 | 73 | SYSTEM_ADMIN 74 | 75 | Example JSON result from the API: 76 | {} 77 | 78 | Expect: HTTP/1.1 200 OK 79 | 80 | :param server: Fully qualified domain name or IP 81 | :param port: SNMP port 82 | :param version: SNMP version ('V2' or 'V3') 83 | :param community: Community name (only needed when version is 'V2') 84 | :param username: Username for authentication 85 | :param auth_protocol: Authentication protocol ('MD5' or 'SHA') 86 | :param auth_passphrase: Authentication passphrase 87 | :param privacy_protocol: Encryption protocol ('DES' or 'AES') 88 | :param privacy_passphrase: Encryption passphrase 89 | """ 90 | # TODO: Add example JSON response 91 | 92 | payload = { 93 | "server": server, 94 | "port": port, 95 | "version": version, 96 | "community": community, 97 | "user_security_model": { 98 | "username": username, 99 | "authentication": { 100 | "protocol": auth_protocol, 101 | "passphrase": auth_passphrase 102 | }, 103 | "privacy": { 104 | "protocol": privacy_protocol, 105 | "passphrase": privacy_passphrase 106 | } 107 | } 108 | } 109 | 110 | log.info("Creating SNMP target") 111 | return self.conn.post('vdc/snmp/config/target', json_payload=payload) 112 | 113 | def update_snmp_target(self, target_id, server, port, version, community, username, 114 | auth_protocol, auth_passphrase, privacy_protocol, 115 | privacy_passphrase): 116 | """ 117 | Adds SNMP Target. 118 | 119 | Required role(s): 120 | 121 | SYSTEM_ADMIN 122 | 123 | There is no response body for this call 124 | 125 | Expect: HTTP/1.1 200 OK 126 | 127 | :param server: Fully qualified domain name or IP 128 | :param port: SNMP port 129 | :param version: SNMP version ('V2' or 'V3') 130 | :param community: Community name (only needed when version is 'V2') 131 | :param username: Username for authentication 132 | :param auth_protocol: Authentication protocol ('MD5' or 'SHA') 133 | :param auth_passphrase: Authentication passphrase 134 | :param privacy_protocol: Encryption protocol ('DES' or 'AES') 135 | :param privacy_passphrase: Encryption passphrase 136 | """ 137 | 138 | payload = { 139 | "server": server, 140 | "port": port, 141 | "version": version, 142 | "community": community, 143 | "user_security_model": { 144 | "username": username, 145 | "authentication": { 146 | "protocol": auth_protocol, 147 | "passphrase": auth_passphrase 148 | }, 149 | "privacy": { 150 | "protocol": privacy_protocol, 151 | "passphrase": privacy_passphrase 152 | } 153 | } 154 | } 155 | 156 | log.info("Updating SNMP target with ID '{}'".format(target_id)) 157 | return self.conn.put('vdc/snmp/config/target/{}'.format(target_id), 158 | json_payload=payload) 159 | 160 | def delete_snmp_target(self, target_id): 161 | """ 162 | Delete specified SNMP target. 163 | 164 | Required role(s): 165 | 166 | SYSTEM_ADMIN 167 | 168 | There is no response body for this call 169 | 170 | Expect: HTTP/1.1 200 OK 171 | 172 | :param target_id: SNMP target ID 173 | """ 174 | log.info("Deleting SNMP target with ID '{}'".format(target_id)) 175 | return self.conn.delete('vdc/snmp/config/target/{}'.format(target_id)) 176 | -------------------------------------------------------------------------------- /ecsclient/v4/configuration/snmp.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class Snmp(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get_snmp_agent(self): 14 | """ 15 | Get SNMP Agent configuration including targets. 16 | 17 | Required role(s): 18 | 19 | SYSTEM_ADMIN 20 | SYSTEM_MONITOR 21 | 22 | Example JSON result from the API: 23 | """ 24 | # TODO: Add example JSON response 25 | log.info("Fetching SNMP Agent configuration") 26 | return self.conn.get('vdc/snmp/config') 27 | 28 | def update_snmp_agent(self, engine_id): 29 | """ 30 | Sets SNMP agent configuration 31 | 32 | Required role(s): 33 | 34 | SYSTEM_ADMIN 35 | 36 | There is no response body for this call 37 | 38 | Expect: HTTP/1.1 200 OK 39 | 40 | :param engine_id: Fully qualified domain name or IP and port 41 | """ 42 | payload = { 43 | "engineID": engine_id 44 | } 45 | log.info("Setting SNMP agent configuration with Engine ID '{}'".format(engine_id)) 46 | return self.conn.put('vdc/snmp/config', json_payload=payload) 47 | 48 | def get_snmp_target(self, target_id): 49 | """ 50 | Get SNMP target info. 51 | 52 | Required role(s): 53 | 54 | SYSTEM_ADMIN 55 | SYSTEM_MONITOR 56 | 57 | Example JSON result from the API: 58 | {} 59 | 60 | :param target_id: SNMP target ID 61 | """ 62 | # TODO: Add example JSON response 63 | log.info("Getting SNMP target information with ID '{}'".format(target_id)) 64 | return self.conn.get('vdc/snmp/config/target/{}'.format(target_id)) 65 | 66 | def create_snmp_target(self, server, port, version, community, username, auth_protocol, 67 | auth_passphrase, privacy_protocol, privacy_passphrase): 68 | """ 69 | Adds SNMP Target. 70 | 71 | Required role(s): 72 | 73 | SYSTEM_ADMIN 74 | 75 | Example JSON result from the API: 76 | {} 77 | 78 | Expect: HTTP/1.1 200 OK 79 | 80 | :param server: Fully qualified domain name or IP 81 | :param port: SNMP port 82 | :param version: SNMP version ('V2' or 'V3') 83 | :param community: Community name (only needed when version is 'V2') 84 | :param username: Username for authentication 85 | :param auth_protocol: Authentication protocol ('MD5' or 'SHA') 86 | :param auth_passphrase: Authentication passphrase 87 | :param privacy_protocol: Encryption protocol ('DES' or 'AES') 88 | :param privacy_passphrase: Encryption passphrase 89 | """ 90 | # TODO: Add example JSON response 91 | 92 | payload = { 93 | "server": server, 94 | "port": port, 95 | "version": version, 96 | "community": community, 97 | "user_security_model": { 98 | "username": username, 99 | "authentication": { 100 | "protocol": auth_protocol, 101 | "passphrase": auth_passphrase 102 | }, 103 | "privacy": { 104 | "protocol": privacy_protocol, 105 | "passphrase": privacy_passphrase 106 | } 107 | } 108 | } 109 | 110 | log.info("Creating SNMP target") 111 | return self.conn.post('vdc/snmp/config/target', json_payload=payload) 112 | 113 | def update_snmp_target(self, target_id, server, port, version, community, username, 114 | auth_protocol, auth_passphrase, privacy_protocol, 115 | privacy_passphrase): 116 | """ 117 | Adds SNMP Target. 118 | 119 | Required role(s): 120 | 121 | SYSTEM_ADMIN 122 | 123 | There is no response body for this call 124 | 125 | Expect: HTTP/1.1 200 OK 126 | 127 | :param server: Fully qualified domain name or IP 128 | :param port: SNMP port 129 | :param version: SNMP version ('V2' or 'V3') 130 | :param community: Community name (only needed when version is 'V2') 131 | :param username: Username for authentication 132 | :param auth_protocol: Authentication protocol ('MD5' or 'SHA') 133 | :param auth_passphrase: Authentication passphrase 134 | :param privacy_protocol: Encryption protocol ('DES' or 'AES') 135 | :param privacy_passphrase: Encryption passphrase 136 | """ 137 | 138 | payload = { 139 | "server": server, 140 | "port": port, 141 | "version": version, 142 | "community": community, 143 | "user_security_model": { 144 | "username": username, 145 | "authentication": { 146 | "protocol": auth_protocol, 147 | "passphrase": auth_passphrase 148 | }, 149 | "privacy": { 150 | "protocol": privacy_protocol, 151 | "passphrase": privacy_passphrase 152 | } 153 | } 154 | } 155 | 156 | log.info("Updating SNMP target with ID '{}'".format(target_id)) 157 | return self.conn.put('vdc/snmp/config/target/{}'.format(target_id), 158 | json_payload=payload) 159 | 160 | def delete_snmp_target(self, target_id): 161 | """ 162 | Delete specified SNMP target. 163 | 164 | Required role(s): 165 | 166 | SYSTEM_ADMIN 167 | 168 | There is no response body for this call 169 | 170 | Expect: HTTP/1.1 200 OK 171 | 172 | :param target_id: SNMP target ID 173 | """ 174 | log.info("Deleting SNMP target with ID '{}'".format(target_id)) 175 | return self.conn.delete('vdc/snmp/config/target/{}'.format(target_id)) 176 | -------------------------------------------------------------------------------- /ecsclient/common/user_management/secret_key.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | class SecretKey(object): 7 | def __init__(self, connection): 8 | """ 9 | Initialize a new instance 10 | """ 11 | self.conn = connection 12 | 13 | def get(self, user_id=None, namespace=None): 14 | """ 15 | Gets all secret keys for the specified user. If the user is not provided, 16 | it will use authenticated user. If the user belongs to a namespace, 17 | the namespace must be supplied 18 | 19 | Required role(s): 20 | 21 | SYSTEM_ADMIN 22 | SYSTEM_MONITOR 23 | NAMESPACE_ADMIN 24 | 25 | Example JSON result from the API: 26 | 27 | { 28 | u'key_timestamp_2': u'2015-05-2105: 43: 33.281', 29 | u'key_timestamp_1': u'2015-05-2105: 43: 30.872', 30 | u'key_expiry_timestamp_2': u'', 31 | u'key_expiry_timestamp_1': u'', 32 | u'secret_key_1': u'3tW4A5G7QaAGVtsGLVL8uDy73S2A6LrvkPTDNdrk', 33 | u'secret_key_2': u'v5LjgoEM9lqHIo+qa7pIhGjrecc/Wv+WVw7kO4oQ', 34 | u'link': { 35 | u'href': u'/object/secret-keys', 36 | u'rel': u'self' 37 | } 38 | } 39 | 40 | :param user_id: Valid user identifier to get the keys from. If not provided, 41 | the authenticated user will be used instead. 42 | :param namespace: Namespace for the user if the user belongs to a namespace (optional) 43 | """ 44 | if user_id: 45 | msg = "Getting secret keys for user '{}'".format(user_id) 46 | url = 'object/user-secret-keys/{}'.format(user_id) 47 | if namespace: 48 | msg += " in namespace '{}'".format(namespace) 49 | url += '/{}'.format(namespace) 50 | else: 51 | msg = "Getting secret keys for the authenticated user" 52 | url = 'object/secret-keys' 53 | 54 | log.info(msg) 55 | return self.conn.get(url) 56 | 57 | def create(self, user_id=None, namespace=None, expiry_time=None, secret_key=None): 58 | """ 59 | Creates a secret key for the specified user. If the user is not provided, 60 | it will create the secret key for the authenticated user. If the user 61 | is provided and belongs to a namespace, the namespace must be supplied. 62 | You may pass in an expiration time in minutes. During the expiration interval, 63 | both keys will be accepted for requests. This gives you a grace period where 64 | you can update applications to use the new key. 65 | 66 | Required role(s): 67 | 68 | SYSTEM_ADMIN 69 | NAMESPACE_ADMIN 70 | 71 | No specific role is required if a user ID is not provided. 72 | 73 | Example JSON result from the API: 74 | 75 | { 76 | "link": { 77 | "rel": "self", 78 | "href":"/object/user-secret-keys/joedeleteme" 79 | }, 80 | "secret_key": "p3PZyb//Ch6tM0fUsnesYYnGb+6JHV8WHzS5YHjg", 81 | "key_timestamp": "2014-12-24 02:08:40.181" 82 | } 83 | 84 | :param user_id: Valid user identifier to create a key for. If not provided, 85 | the authenticated user will be used instead. 86 | :param namespace: Namespace for the user if the user belongs to a namespace (optional) 87 | :param expiry_time: Expiry time in minutes for the previous secret key. Note that nodes may 88 | cache secret keys for up to two minutes so old keys may not expire immediately. 89 | Do not provide an expiry time if the user does not have an existing key (optional) 90 | :param secret_key: Secret key associated with this user. If not provided, system 91 | will generate one 92 | """ 93 | 94 | payload = dict() 95 | 96 | if expiry_time: 97 | payload["existing_key_expiry_time_mins"] = expiry_time 98 | 99 | if user_id: 100 | url = 'object/user-secret-keys/{}'.format(user_id) 101 | msg = "Creating secret for user '{}'".format(user_id) 102 | if namespace: 103 | payload['namespace'] = namespace 104 | if secret_key: 105 | payload['secretkey'] = secret_key 106 | else: 107 | url = 'object/secret-keys' 108 | msg = "Creating secret for the authenticated user" 109 | if secret_key: 110 | log.warning("Ignoring provided secret key. Cannot set custom secret key for authenticated user") 111 | 112 | log.info(msg) 113 | return self.conn.post(url, json_payload=payload) 114 | 115 | def delete(self, user_id=None, namespace=None, secret_key=None): 116 | """ 117 | Deletes all secret keys for the specific user. If the user is not provided, 118 | it will delete the secret keys for the authenticated user. If the user is 119 | provided and belongs namespace, the namespace must be supplied. 120 | 121 | Required role(s): 122 | 123 | SYSTEM_ADMIN 124 | NAMESPACE_ADMIN 125 | 126 | Example JSON result from the API: 127 | 128 | There is no response body for this call 129 | 130 | Expect: HTTP/1.1 200 OK 131 | 132 | :param user_id: Valid user identifier to get delete the keys from (optional) 133 | :param namespace: Namespace for the user if the user belongs to a namespace (optional) 134 | :param secret_key: The secret key to deleted (optional) 135 | """ 136 | 137 | if user_id: 138 | url = 'object/user-secret-keys/{}/deactivate'.format(user_id) 139 | msg = "Deleting secret for user '{}'".format(user_id) 140 | else: 141 | url = 'object/secret-keys/deactivate' 142 | msg = "Deleting secret for the authenticated user" 143 | 144 | payload = {} 145 | if secret_key: 146 | payload['secret_key'] = secret_key 147 | if namespace: 148 | payload['namespace'] = namespace 149 | 150 | log.info(msg) 151 | return self.conn.post(url, json_payload=payload) 152 | -------------------------------------------------------------------------------- /tests/unit/test_authentication.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import testtools 3 | from mock import mock 4 | from requests.auth import _basic_auth_str 5 | from requests_mock.contrib import fixture 6 | 7 | from ecsclient.baseclient import Client 8 | from ecsclient.common.exceptions import ECSClientException 9 | 10 | 11 | class TestAuthentication(testtools.TestCase): 12 | LOGIN_URL = 'http://127.0.0.1:4443/login' 13 | LOGOUT_URL = 'http://127.0.0.1:4443/logout' 14 | 15 | def setUp(self, *args, **kwargs): 16 | # logging.basicConfig(level=logging.DEBUG) 17 | super(TestAuthentication, self).setUp(*args, **kwargs) 18 | self.client = Client(username='someone', 19 | password='password', 20 | ecs_endpoint='http://127.0.0.1:4443', 21 | token_endpoint='http://127.0.0.1:4443/login') 22 | self.requests_mock = self.useFixture(fixture.Fixture()) 23 | 24 | def test_get_token_valid_credentials(self): 25 | self.requests_mock.register_uri('GET', self.LOGIN_URL, headers={'X-SDS-AUTH-TOKEN': 'FAKE-TOKEN-123'}) 26 | self.assertIsNone(self.client.token) 27 | 28 | token = self.client.get_token() 29 | 30 | self.assertEqual(token, 'FAKE-TOKEN-123') 31 | self.assertEqual(self.client._token_request.token, 'FAKE-TOKEN-123') 32 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 33 | self.assertEqual(self.requests_mock.last_request.url, self.LOGIN_URL) 34 | self.assertEqual(self.requests_mock.last_request.headers['authorization'], 35 | _basic_auth_str('someone', 'password')) 36 | 37 | def test_get_token_invalid_credentials(self): 38 | self.requests_mock.register_uri('GET', self.LOGIN_URL, status_code=401, text='body') 39 | 40 | with super(testtools.TestCase, self).assertRaises(ECSClientException) as error: 41 | self.client.get_token() 42 | 43 | exception = error.exception 44 | self.assertIsNone(self.client._token_request.token) 45 | self.assertEqual(exception.message, 'Invalid username or password') 46 | self.assertEqual(exception.http_response_content, 'body') 47 | self.assertEqual(exception.http_status, 401) 48 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 49 | self.assertEqual(self.requests_mock.last_request.url, self.LOGIN_URL) 50 | self.assertEqual(self.requests_mock.last_request.headers['authorization'], 51 | _basic_auth_str('someone', 'password')) 52 | 53 | @mock.patch('ecsclient.baseclient.os.remove') 54 | @mock.patch('ecsclient.baseclient.os.path.isfile') 55 | def test_logout(self, mock_isfile, mock_remove): 56 | self.client.token = 'FAKE-TOKEN-123' 57 | self.client._token_request.token = 'FAKE-TOKEN-123' 58 | self.requests_mock.register_uri('GET', self.LOGOUT_URL, text="{'user': 'someone'}") 59 | mock_isfile.return_value = True 60 | mock_remove.return_value = True 61 | 62 | resp = self.client.authentication.logout() 63 | 64 | self.assertEqual(resp, "{'user': 'someone'}") 65 | self.assertIsNone(self.client.token) 66 | self.assertIsNone(self.client._token_request.token) 67 | mock_isfile.assert_called_with('/tmp/ecsclient.tkn') 68 | mock_remove.assert_called_with('/tmp/ecsclient.tkn') 69 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 70 | self.assertEqual(self.requests_mock.last_request.url, self.LOGOUT_URL) 71 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'FAKE-TOKEN-123') 72 | 73 | @mock.patch('ecsclient.baseclient.os.remove') 74 | @mock.patch('ecsclient.baseclient.os.path.isfile') 75 | def test_logout_force(self, mock_isfile, mock_remove): 76 | self.client.token = 'FAKE-TOKEN-123' 77 | self.client._token_request.token = 'FAKE-TOKEN-123' 78 | self.requests_mock.register_uri('GET', self.LOGOUT_URL + '?force=True', text="{'user': 'someone'}") 79 | mock_isfile.return_value = True 80 | mock_remove.return_value = True 81 | 82 | resp = self.client.authentication.logout(force=True) 83 | 84 | self.assertEqual(resp, "{'user': 'someone'}") 85 | self.assertIsNone(self.client.token) 86 | self.assertIsNone(self.client._token_request.token) 87 | mock_isfile.assert_called_with('/tmp/ecsclient.tkn') 88 | mock_remove.assert_called_with('/tmp/ecsclient.tkn') 89 | self.assertEqual(self.requests_mock.last_request.method, 'GET') 90 | self.assertEqual(self.requests_mock.last_request.url, self.LOGOUT_URL + '?force=True') 91 | self.assertEqual(self.requests_mock.last_request.qs['force'], ['true']) 92 | self.assertEqual(self.requests_mock.last_request.headers['x-sds-auth-token'], 'FAKE-TOKEN-123') 93 | 94 | def test_logout_when_logged_out(self): 95 | self.client._token_request.token = 'FAKE-TOKEN-123' 96 | self.client._token_request.cache_token = False 97 | self.requests_mock.register_uri('GET', self.LOGOUT_URL, text="{'user': 'someone'}") 98 | self.requests_mock.register_uri('GET', 'http://127.0.0.1:4443/user/whoami') 99 | 100 | resp = self.client.authentication.logout() 101 | 102 | self.assertEqual(resp, "{'user': 'someone'}") 103 | 104 | resp2 = self.client.authentication.logout() 105 | 106 | self.assertIsNone(resp2) 107 | 108 | def test_logout_and_reconnect(self): 109 | self.client.token = 'FAKE-TOKEN-123' 110 | self.client._token_request.token = 'FAKE-TOKEN-123' 111 | self.client._token_request.cache_token = False 112 | self.requests_mock.register_uri('GET', self.LOGOUT_URL, text="{'user': 'someone'}") 113 | 114 | self.client.authentication.logout() 115 | 116 | self.assertIsNone(self.client.token) 117 | self.assertIsNone(self.client._token_request.token) 118 | 119 | self.requests_mock.register_uri('GET', self.LOGIN_URL, headers={'X-SDS-AUTH-TOKEN': 'NEW-TOKEN-123'}) 120 | 121 | self.client.get('login') 122 | 123 | self.assertEqual(self.client._token_request.token, 'NEW-TOKEN-123') 124 | 125 | 126 | -------------------------------------------------------------------------------- /ecsclient/common/provisioning/base_url.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | class BaseUrl(object): 15 | 16 | def __init__(self, connection): 17 | """ 18 | Initialize a new instance 19 | """ 20 | self.conn = connection 21 | 22 | def get_all_configured_base_urls(self): 23 | """ 24 | Lists all configured Base URLs. 25 | 26 | Required role(s): 27 | 28 | SYSTEM_ADMIN 29 | SYSTEM_MONITOR 30 | 31 | Example JSON result from the API: 32 | 33 | { 34 | u'base_url': [ 35 | { 36 | u'link': { 37 | u'href': u'/object/baseurl/urn: ObjectBaseUrl: 38 | 6c74e6fb-a2a1-4386-bc25-b4399a6e74ce', 39 | u'rel': u'self' 40 | }, 41 | u'name': u'US1', 42 | u'id': u'urn: ObjectBaseUrl: 43 | 6c74e6fb-a2a1-4386-bc25-b4399a6e74ce' 44 | }, 45 | { 46 | u'link': { 47 | u'href': u'/object/baseurl/urn: ObjectBaseUrl: 48 | 70f63a6f-25be-432c-875f-61c4a3953c42', 49 | u'rel': u'self' 50 | }, 51 | u'name': u'DefaultBaseUrl', 52 | u'id': u'urn: ObjectBaseUrl: 53 | 70f63a6f-25be-432c-875f-61c4a3953c42' 54 | }, 55 | { 56 | u'link': { 57 | u'href': u'/object/baseurl/urn: ObjectBaseUrl: 58 | 72863b71-27a8-4917-8df0-cc84d3bdfe98', 59 | u'rel': u'self' 60 | }, 61 | u'name': u'US2', 62 | u'id': u'urn: ObjectBaseUrl: 63 | 72863b71-27a8-4917-8df0-cc84d3bdfe98' 64 | } 65 | ] 66 | } 67 | """ 68 | log.info("Getting all Base URLs") 69 | return self.conn.get(url='object/baseurl') 70 | 71 | def get_base_url(self, base_url_id): 72 | """ 73 | Gets details for the specified Base URL. 74 | 75 | Required role(s): 76 | 77 | SYSTEM_ADMIN 78 | SYSTEM_MONITOR 79 | 80 | Example JSON result from the API: 81 | 82 | { 83 | u'remote': None, 84 | u'name': u'US1', 85 | u'tags': [ 86 | 87 | ], 88 | u'global': None, 89 | u'baseUrlNamespaceInHostFlag': False, 90 | u'baseurl': u'os-us1.rraas-ops.com', 91 | u'vdc': None, 92 | u'link': { 93 | u'href': u'/object/baseurl/urn: ObjectBaseUrl: 94 | 6c74e6fb-a2a1-4386-bc25-b4399a6e74ce', 95 | u'rel': u'self' 96 | }, 97 | u'id': u'urn: ObjectBaseUrl: 6c74e6fb-a2a1-4386-bc25-b4399a6e74ce' 98 | } 99 | 100 | :param base_url_id: Base URL identifier for the Base URL that needs to 101 | be retrieved 102 | """ 103 | log.info("Getting Base URL '{0}'".format(base_url_id)) 104 | return self.conn.get(url='object/baseurl/{0}'.format(base_url_id)) 105 | 106 | def create_base_url(self, name, base_url, is_namespace_in_host=True): 107 | """ 108 | Creates a Base URL with the given details. 109 | 110 | Required role(s): 111 | 112 | SYSTEM_ADMIN 113 | 114 | Example JSON result from the API: 115 | 116 | { 117 | u'remote': None, 118 | u'name': u'TestBaseURL', 119 | u'tags': [ 120 | 121 | ], 122 | u'global': None, 123 | u'baseUrlNamespaceInHostFlag': False, 124 | u'baseurl': u'test.com', 125 | u'vdc': None, 126 | u'link': { 127 | u'href': u'/object/baseurl/urn: ObjectBaseUrl: 128 | 19c391eb-37f4-4c65-a7a9-474668f71607', 129 | u'rel': u'self' 130 | }, 131 | u'id': u'urn: ObjectBaseUrl: 19c391eb-37f4-4c65-a7a9-474668f71607' 132 | } 133 | 134 | :param name: Name for this Base-URL 135 | :param base_url: Base URL to be used 136 | :param is_namespace_in_host: Set true if namespace is in host, false 137 | otherwise 138 | """ 139 | payload = { 140 | "name": name, 141 | "base_url": base_url, 142 | "is_namespace_in_host": is_namespace_in_host 143 | } 144 | log.info("Creating Base URL '{0}': {1}".format(name, payload)) 145 | return self.conn.post(url='object/baseurl', json_payload=payload) 146 | 147 | def delete_base_url(self, base_url_id): 148 | """ 149 | Updates the owner for the specified bucket. 150 | 151 | Required role(s): 152 | 153 | SYSTEM_ADMIN 154 | 155 | Example JSON result from the API: 156 | 157 | There is no response body for this call 158 | 159 | Expect: HTTP/1.1 200 OK 160 | 161 | :param base_url_id: Base URL identifier that needs to be deleted 162 | """ 163 | log.info("Deleting Base URL '{0}'".format(base_url_id)) 164 | 165 | return self.conn.post( 166 | url='object/baseurl/{0}/deactivate'.format(base_url_id)) 167 | 168 | def modify_base_url(self, base_url_id, name, base_url, 169 | is_namespace_in_host=True): 170 | """ 171 | Updates the Base URL for the specified Base URL identifier. 172 | 173 | Required role(s): 174 | 175 | SYSTEM_ADMIN 176 | 177 | Example JSON result from the API: 178 | 179 | There is no response body for this call 180 | 181 | Expect: HTTP/1.1 200 OK 182 | 183 | :param base_url_id: Base URL identifier that needs to be updated 184 | :param name: Name for this Base-URL 185 | :param base_url: Base URL to be used 186 | :param is_namespace_in_host: Set true if namespace is in host, false 187 | otherwise 188 | """ 189 | payload = { 190 | "name": name, 191 | "base_url": base_url, 192 | "is_namespace_in_host": is_namespace_in_host 193 | } 194 | 195 | log.info("Updating Base URL '{0}': {1}".format(base_url_id, payload)) 196 | 197 | return self.conn.put( 198 | url='object/baseurl/{0}'.format(base_url_id), json_payload=payload) 199 | -------------------------------------------------------------------------------- /ecsclient/common/multitenancy/tenant.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | """ 6 | Tenant APIs only supported in ECS Flex 7 | """ 8 | 9 | 10 | class Tenant(object): 11 | def __init__(self, connection): 12 | """ 13 | Initialize a new instance 14 | """ 15 | self.conn = connection 16 | 17 | def list(self): 18 | """ 19 | Gets the identifiers for all configured tenants. 20 | 21 | Required role(s): 22 | 23 | SYSTEM_ADMIN 24 | SYSTEM_MONITOR 25 | 26 | Example JSON result from the API: 27 | 28 | { 29 | u'tenant': [ 30 | { 31 | u'id': u'tenant1' 32 | } 33 | ] 34 | } 35 | """ 36 | log.info("Getting all tenants") 37 | return self.conn.get(url='object/tenants') 38 | 39 | def get(self, tenant): 40 | """ 41 | Gets the details for the given tenant. 42 | 43 | Required role(s): 44 | 45 | SYSTEM_ADMIN 46 | SYSTEM_MONITOR 47 | TENANT_ADMIN 48 | 49 | :param tenant: Tenant identifier for which details needs to 50 | be retrieved. 51 | """ 52 | log.info("Getting info for tenant '{0}'".format(tenant)) 53 | 54 | return self.conn.get( 55 | url='object/tenants/tenant/{0}'.format(tenant)) 56 | 57 | def create(self, account, default_data_services_vpool=None, 58 | is_encryption_enabled=False, default_bucket_block_size=None): 59 | """ 60 | Creates a namespace with the given details 61 | 62 | Required role(s): 63 | 64 | SYSTEM_ADMIN 65 | 66 | Example JSON result from the API: 67 | { 68 | 'account_id':account_id, 69 | 'default_data_services_vpool': default_data_services_vpool, 70 | 'default_bucket_block_size': default_bucket_block_size 71 | 'is_encryption_enabled': is_encryption_enabled 72 | } 73 | """ 74 | payload = { 75 | 'account_id': account, 76 | 'default_bucket_block_size': default_bucket_block_size, 77 | 'default_data_services_vpool': default_data_services_vpool, 78 | 'is_encryption_enabled': is_encryption_enabled, 79 | } 80 | log.info("Creating tenant for account '{0}'".format(account)) 81 | return self.conn.post('object/tenants/tenant', json_payload=payload) 82 | 83 | # def update(self, tenant_id, default_data_services_vpool=None, vpools_added_to_allowed_vpools_list=[], 84 | # vpools_added_to_disallowed_vpools_list=[], vpools_removed_from_allowed_vpools_list=[], 85 | # vpools_removed_from_disallowed_vpools_list=[], tenant_admins=None, user_mapping=None, 86 | # default_bucket_block_size=None, external_group_admins=None, is_encryption_enabled=None, 87 | # is_stale_allowed=None): 88 | # """ 89 | # Updates tenant details like replication group list, tenant admins and user mappings. 90 | # Replication group can be: 91 | # - Added to allowed or disallowed replication group list 92 | # - Removed from allowed or disallowed replication group list 93 | # 94 | # Required role(s): 95 | # 96 | # SYSTEM_ADMIN 97 | # TENANT_ADMIN 98 | # 99 | # There is no response body for this call 100 | # 101 | # Expect: HTTP/1.1 200 OK 102 | # 103 | # :param tenant_id: Tenant identifier whose details needs to be updated 104 | # :param default_data_services_vpool: Default replication group identifier when creating buckets 105 | # :param vpools_added_to_allowed_vpools_list: List of replication group identifier which will be added in the 106 | # allowed List for allowing tenant access 107 | # :param vpools_added_to_disallowed_vpools_list: List of replication group identifier which will be added in the 108 | # disallowed list for prohibiting tenant access 109 | # :param vpools_removed_from_allowed_vpools_list: List of replication group identifier which will be removed 110 | # from allowed list 111 | # :param vpools_removed_from_disallowed_vpools_list: List of replication group identifier which will be removed 112 | # from disallowed list for removing their prohibition tenant access 113 | # :param tenant_admins: Comma separated list of tenant admins 114 | # :param user_mapping: List of user mapping objects 115 | # :param default_bucket_block_size: Default bucket quota size 116 | # :param external_group_admins: List of groups from AD Server 117 | # :param is_encryption_enabled: Update encryption for the tenant. If null then encryption will not be updated. 118 | # :param is_stale_allowed: Flag to allow stale data within the tenant. If null then stale allowance will not be 119 | # updated 120 | # """ 121 | # payload = { 122 | # "default_data_services_vpool": default_data_services_vpool, 123 | # "vpools_added_to_allowed_vpools_list": vpools_added_to_allowed_vpools_list, 124 | # "vpools_added_to_disallowed_vpools_list": vpools_added_to_disallowed_vpools_list, 125 | # "vpools_removed_from_allowed_vpools_list": vpools_removed_from_allowed_vpools_list, 126 | # "vpools_removed_from_disallowed_vpools_list": vpools_removed_from_disallowed_vpools_list, 127 | # "tenant_admins": tenant_admins, 128 | # "user_mapping": user_mapping, 129 | # "default_bucket_block_size": default_bucket_block_size, 130 | # "external_group_admins": external_group_admins, 131 | # "is_encryption_enabled": is_encryption_enabled, 132 | # "is_stale_allowed": is_stale_allowed 133 | # } 134 | # # FIXME: According to the API, this call should return the updated object, but it does not 135 | # log.info("Updating tenant ID '{}'".format(tenant_id)) 136 | # return self.conn.put('object/tenants/tenant/{}'.format(tenant_id), json_payload=payload) 137 | 138 | def delete(self, tenant_id): 139 | """ 140 | Deactivates and deletes the given tenant. 141 | 142 | Required role(s): 143 | 144 | SYSTEM_ADMIN 145 | 146 | There is no response body for this call 147 | 148 | Expect: HTTP/1.1 200 OK 149 | 150 | :param tenant_id: An active tenant identifier which needs to be deleted 151 | """ 152 | log.info("Deleting tenant ID '{}'".format(tenant_id)) 153 | # FIXME: This should be a DELETE request 154 | return self.conn.post('object/tenants/tenant/{}/delete'.format(tenant_id)) 155 | -------------------------------------------------------------------------------- /ecsclient/common/token_request.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | import logging 3 | import os 4 | 5 | # Third party imports 6 | import requests 7 | 8 | # Project level imports 9 | from ecsclient.common.exceptions import ECSClientException 10 | 11 | 12 | # Suppress the insecure request warning 13 | # https://urllib3.readthedocs.org/en/ 14 | # latest/security.html#insecurerequestwarning 15 | requests.packages.urllib3.disable_warnings() 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | 20 | class TokenRequest(object): 21 | """ 22 | This is a helper class to fetch a new token from the ECS controller 23 | and return the token as well as store it locally. Prior to fetching a new 24 | token we check if we have a local token and if so, whether or not it is 25 | still valid 26 | """ 27 | 28 | def __init__(self, username, password, ecs_endpoint, token_endpoint, 29 | verify_ssl, token_path, request_timeout, 30 | cache_token): 31 | """ 32 | Create a new TokenRequest instance 33 | 34 | :param username: The username to fetch a token 35 | :param password: The password to fetch a token 36 | :param ecs_endpoint: The URL where ECS is located 37 | :param token_endpoint: The URL where the ECS login is located 38 | :param verify_ssl: Verify SSL certificates 39 | :param token_path: Path to the cached token file 40 | :param request_timeout: How long to wait for ECS to respond 41 | :param cache_token: Whether to cache the token, by default this is true 42 | you should only switch this to false when you want to directly fetch 43 | a token for a user 44 | """ 45 | self.username = username 46 | self.password = password 47 | self.ecs_endpoint = ecs_endpoint 48 | self.token_endpoint = token_endpoint 49 | self.verify_ssl = verify_ssl 50 | self.token_verification_endpoint = ecs_endpoint + '/user/whoami' 51 | self.token_path = token_path 52 | self.request_timeout = request_timeout 53 | self.cache_token = cache_token 54 | self.token = None 55 | self.session = requests.Session() 56 | 57 | def get_new_token(self): 58 | """ 59 | Request a new authentication token from ECS and persist it 60 | to a file for future usage if cache_token is true 61 | 62 | :return: Returns a valid token, or None if failed 63 | """ 64 | log.info("Getting new token") 65 | self.session.auth = (self.username, self.password) 66 | 67 | req = self.session.get(self.token_endpoint, 68 | verify=self.verify_ssl, 69 | headers={'Accept': 'application/json'}, 70 | timeout=self.request_timeout) 71 | 72 | if req.status_code == 401: 73 | msg = 'Invalid username or password' 74 | log.fatal(msg) 75 | raise ECSClientException.from_response(req, message=msg) 76 | if req.status_code != 200: 77 | msg = 'Non-200 status returned ({0})'.format(req.status_code) 78 | log.fatal(msg) 79 | raise ECSClientException.from_response(req, message=msg) 80 | 81 | self.token = req.headers['x-sds-auth-token'] 82 | 83 | if self.cache_token: 84 | log.debug("Caching token to '{0}'".format(self.token_path)) 85 | 86 | token_dir = os.path.dirname(os.path.abspath(self.token_path)) 87 | if not os.path.isdir(token_dir): 88 | raise ECSClientException('Token directory not found') 89 | 90 | with open(self.token_path, 'w') as token_file: 91 | token_file.write(self.token) 92 | 93 | return self.token 94 | 95 | def get_token(self): 96 | """ 97 | Attempt to get an existing token, if successful then ensure it 98 | hasn't expired yet. If its expired, fetch a new token 99 | 100 | :return: A token 101 | """ 102 | token = self._get_existing_token() 103 | 104 | if not token: 105 | log.debug("No Token found getting new one") 106 | return self.get_new_token() 107 | 108 | # FIXME: Avoid validation at every call 109 | log.debug("Validating token") 110 | req = self._request(token, self.token_verification_endpoint) 111 | 112 | if req.status_code == 200: 113 | log.debug("Token validated successfully") 114 | return token 115 | elif req.status_code in (401, 403, 415): 116 | msg = "Invalid token. Trying to get a new one (Code: {})".format(req.status_code) 117 | log.warning(msg) 118 | return self.get_new_token() 119 | else: # i.e. 500 or unknown raise an exception 120 | msg = "Token validation error (Code: {})".format(req.status_code) 121 | log.error(msg) 122 | raise ECSClientException.from_response(req, message=msg) 123 | 124 | def _get_existing_token(self): 125 | """ 126 | Attempt to open and read the token file if it exists 127 | 128 | :return: If available return the token, if not return None 129 | """ 130 | 131 | token = self.token 132 | 133 | if not token and self.cache_token: 134 | if os.path.isfile(self.token_path): 135 | log.debug("Reading cached token at '{0}'".format(self.token_path)) 136 | with open(self.token_path, 'r') as token_file: 137 | token = token_file.read() 138 | 139 | if not token: 140 | log.debug("No token found") 141 | 142 | return token 143 | 144 | def _request(self, token, url): 145 | """ 146 | Perform a request and place the token header inside the request header 147 | 148 | :param token: A valid token 149 | :param url: The URL to perform a request 150 | :return: Request Object 151 | """ 152 | headers = {'Accept': 'application/json', 153 | 'X-SDS-AUTH-TOKEN': token} 154 | try: 155 | return self.session.get(url, verify=self.verify_ssl, 156 | headers=headers, 157 | timeout=self.request_timeout) 158 | except requests.ConnectionError as conn_err: 159 | msg = 'Connection error: {0}'.format(conn_err.args) 160 | log.error(msg) 161 | raise ECSClientException(msg) 162 | except requests.HTTPError as http_err: 163 | msg = 'HTTP error: {0}'.format(http_err.args) 164 | log.error(msg) 165 | raise ECSClientException(msg) 166 | except requests.RequestException as req_err: 167 | msg = 'Request error: {0}'.format(req_err.args) 168 | log.error(msg) 169 | raise ECSClientException(msg) 170 | -------------------------------------------------------------------------------- /ecsclient/common/user_management/object_user.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | class ObjectUser(object): 8 | 9 | def __init__(self, connection): 10 | """ 11 | Initialize a new instance 12 | """ 13 | self.conn = connection 14 | 15 | def list(self, namespace=None): 16 | """ 17 | Gets identifiers for all configured users. If namespace is provided 18 | then returns all users for the specified namespace. 19 | 20 | Required role(s): 21 | 22 | SYSTEM_ADMIN 23 | SYSTEM_MONITOR 24 | NAMESPACE_ADMIN 25 | 26 | Example JSON result from the API: 27 | 28 | { 29 | u'blobuser': [ 30 | { 31 | u'userid': u'johndoe', 32 | u'namespace': u'namespace1' 33 | }, 34 | { 35 | u'userid': u'janedoe', 36 | u'namespace': u'namespace1' 37 | } 38 | ] 39 | } 40 | 41 | :param namespace: Namespace for which users should be returned. Optional. 42 | """ 43 | msg = 'Listing all object users' 44 | url = 'object/users' 45 | 46 | if namespace: 47 | url += '/{}'.format(namespace) 48 | msg += " in namespace '{}'".format(namespace) 49 | 50 | log.info(msg) 51 | return self.conn.get(url=url) 52 | 53 | def get(self, user_id, namespace=None): 54 | """ 55 | Gets user details for the specified user. 56 | 57 | Required role(s): 58 | 59 | SYSTEM_ADMIN 60 | SYSTEM_MONITOR 61 | NAMESPACE_ADMIN 62 | 63 | Example JSON result from the API: 64 | 65 | { 66 | u'locked': False, 67 | u'namespace': u'namespace1', 68 | u'name': u'someone', 69 | u'created': u'ThuMay2105: 43: 27UTC2015' 70 | } 71 | 72 | :param user_id: Valid user identifier 73 | :param namespace: Optional when user scope is GLOBAL. Required when 74 | user scope is NAMESPACE. The namespace to which the user belongs 75 | """ 76 | msg = "Getting user ID '{}'".format(user_id) 77 | url = 'object/users/{}/info'.format(user_id) 78 | 79 | if namespace: 80 | url += '?namespace={}'.format(namespace) 81 | msg += " in namespace '{}'".format(namespace) 82 | 83 | log.info(msg) 84 | return self.conn.get(url=url) 85 | 86 | def delete(self, user_id, namespace=None): 87 | """ 88 | Deletes the specified user and its secret keys. 89 | 90 | Required role(s): 91 | 92 | SYSTEM_ADMIN 93 | NAMESPACE_ADMIN 94 | 95 | There is no response body for this call 96 | 97 | Expect: HTTP/1.1 200 OK 98 | 99 | :param user_id: Valid user identifier 100 | :param namespace: Example: namespace1 (optional) 101 | """ 102 | payload = {"user": user_id} 103 | if namespace: 104 | payload["namespace"] = namespace 105 | 106 | log.info("Deleting user ID '{}'".format(user_id)) 107 | return self.conn.post('object/users/deactivate', json_payload=payload) 108 | 109 | def create(self, user_id, namespace, tags=None): 110 | """ 111 | Creates a user for a specified namespace. The user must subsequently 112 | be assigned a secret key in order to access the object store. 113 | 114 | Required role(s): 115 | 116 | SYSTEM_ADMIN 117 | NAMESPACE_ADMIN 118 | 119 | Example JSON result from the API: 120 | 121 | { 122 | "link": { 123 | "href": "/object/user-secret-keys/wuser1@sanity.local", 124 | "rel": "self" 125 | } 126 | } 127 | 128 | :param user_id: User to be created 129 | :param namespace: Namespace identifier to associate with the user 130 | :param tags: A list of arbitrary tags to assign to the new user. These can 131 | be used to track additional information about the user and will also 132 | appear on bucket billing responses for buckets owned by the user 133 | """ 134 | 135 | payload = { 136 | "user": user_id, 137 | "namespace": namespace 138 | } 139 | 140 | if tags: 141 | payload['tags'] = tags 142 | 143 | log.info('Creating user: {0}'.format(payload)) 144 | return self.conn.post('object/users', json_payload=payload) 145 | 146 | def lock(self, user_id, namespace=None): 147 | """ 148 | Locks the specified user. If the user belongs to a 149 | namespace, the namespace must be supplied. 150 | 151 | Required role(s): 152 | 153 | SYSTEM_ADMIN 154 | NAMESPACE_ADMIN 155 | 156 | There is no response body for this call 157 | 158 | Expect: HTTP/1.1 200 OK 159 | 160 | :param user_id: User name to be locked 161 | :param namespace: Namespace for this user (optional) 162 | """ 163 | return self.__lock(user_id, namespace, True) 164 | 165 | def unlock(self, user_id, namespace=None): 166 | """ 167 | Unlocks the specified user. If the user belongs to a 168 | namespace, the namespace must be supplied. 169 | 170 | Required role(s): 171 | 172 | SYSTEM_ADMIN 173 | NAMESPACE_ADMIN 174 | 175 | There is no response body for this call 176 | 177 | Expect: HTTP/1.1 200 OK 178 | 179 | :param user_id: User name to be unlocked 180 | :param namespace: Namespace for this user (optional) 181 | """ 182 | return self.__lock(user_id, namespace, False) 183 | 184 | def __lock(self, user_id, namespace, lock): 185 | payload = { 186 | "user": user_id, 187 | "isLocked": lock 188 | } 189 | 190 | if namespace: 191 | payload["namespace"] = namespace 192 | 193 | verb = "Locking" if lock else "Unlocking" 194 | log.info("{} user ID {}".format(verb, user_id)) 195 | return self.conn.put(url='object/users/lock', json_payload=payload) 196 | 197 | def get_lock(self, user_id, namespace=None): 198 | """ 199 | Gets the user lock state for the specified user belonging to the 200 | specified namespace (if provided). 201 | 202 | Required role(s): 203 | 204 | SYSTEM_ADMIN 205 | SYSTEM_MONITOR 206 | NAMESPACE_ADMIN 207 | 208 | Example JSON result from the API: 209 | 210 | { 211 | u'user_name': u'testlogin', 212 | u'isLocked': False 213 | } 214 | 215 | :param user_id: User ID for which user lock status should be returned 216 | :param namespace: Namespace to which user belongs (optional) 217 | """ 218 | msg = "Getting lock state for user ID '{}'".format(user_id) 219 | url = 'object/users/lock/{}'.format(user_id) 220 | if namespace: 221 | msg += " in namespace '{}'".format(namespace) 222 | url += '/{}'.format(namespace) 223 | 224 | log.info(msg) 225 | return self.conn.get(url) 226 | -------------------------------------------------------------------------------- /ecsclient/baseclient.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | import os 4 | 5 | import requests 6 | 7 | from ecsclient.authentication import Authentication 8 | from ecsclient.common.exceptions import ECSClientException 9 | from ecsclient.common.token_request import TokenRequest 10 | 11 | # Suppress the insecure request warning 12 | # https://urllib3.readthedocs.org/en/ 13 | # latest/security.html#insecurerequestwarning 14 | requests.packages.urllib3.disable_warnings() 15 | 16 | # Initialize logger 17 | log = logging.getLogger(__name__) 18 | log.addHandler(logging.NullHandler()) 19 | 20 | 21 | class Client(object): 22 | 23 | def __init__(self, username=None, password=None, token=None, 24 | ecs_endpoint=None, token_endpoint=None, verify_ssl=False, 25 | token_path='/tmp/ecsclient.tkn', 26 | request_timeout=15.0, cache_token=True, override_header=None): 27 | """ 28 | Creates the ECSClient class that the client will directly work with 29 | 30 | :param username: 31 | The username to fetch a token 32 | :param password: The password to fetch a token 33 | :param token: Supply a valid token to use instead of username/password 34 | :param ecs_endpoint: The URL where ECS is located 35 | :param token_endpoint: The URL where the ECS login is located 36 | :param verify_ssl: Verify SSL certificates 37 | :param token_path: Path to the cached token file 38 | :param request_timeout: How long to wait for ECS to respond 39 | :param cache_token: Whether to cache the token, by default this is true 40 | you should only switch this to false when you want to directly fetch 41 | a token for a user 42 | :param override_header: X-EMC-Override header value into API calls 43 | """ 44 | if not ecs_endpoint: 45 | raise ECSClientException("Missing 'ecs_endpoint'") 46 | 47 | self.token_endpoint = token_endpoint 48 | 49 | if token_endpoint: 50 | if not (username and password): 51 | raise ECSClientException("'token_endpoint' provided but missing ('username','password')") 52 | self.token_endpoint = self.token_endpoint.rstrip('/') 53 | else: 54 | if not (token or os.path.isfile(token_path)): 55 | raise ECSClientException("'token_endpoint' not provided and missing 'token'|'token_path'") 56 | 57 | self.override_header = override_header 58 | self.username = username 59 | self.password = password 60 | self.token = token 61 | self.ecs_endpoint = ecs_endpoint.rstrip('/') 62 | self.verify_ssl = verify_ssl 63 | self.token_path = token_path 64 | self.request_timeout = request_timeout 65 | self.cache_token = cache_token 66 | self._session = requests.Session() 67 | self._token_request = TokenRequest( 68 | username=self.username, 69 | password=self.password, 70 | ecs_endpoint=self.ecs_endpoint, 71 | token_endpoint=self.token_endpoint, 72 | verify_ssl=self.verify_ssl, 73 | token_path=self.token_path, 74 | request_timeout=self.request_timeout, 75 | cache_token=self.cache_token) 76 | 77 | # Authentication 78 | self.authentication = Authentication(self) 79 | 80 | def get_token(self): 81 | """ 82 | Get a token directly back, typically you want to set the cache_token 83 | param for ecsclient to false for this call. 84 | 85 | :return: A valid token or an ecsclient exception 86 | """ 87 | return self._token_request.get_new_token() 88 | 89 | def get_current_token(self): 90 | """ 91 | Get the current token in use. None if the client is logged out or not yet logged in 92 | """ 93 | return self._token_request.token 94 | 95 | def remove_cached_token(self): 96 | """ 97 | Remove the cached token file, this is useful if you switch users 98 | and want to use a different token 99 | """ 100 | self.token = None 101 | self._token_request.token = None 102 | 103 | if os.path.isfile(self.token_path): 104 | log.debug("Removing cached token '{0}'".format(self.token_path)) 105 | os.remove(self.token_path) 106 | 107 | def _fetch_headers(self): 108 | token = self.token if self.token else self._token_request.get_token() 109 | headers = {'Accept': 'application/json', 110 | 'Content-Type': 'application/json', 111 | 'x-sds-auth-token': token} 112 | if self.override_header is not None: 113 | headers['X-EMC-Override'] = self.override_header 114 | return headers 115 | 116 | def _construct_url(self, path): 117 | url = '{0}/{1}'.format(self.ecs_endpoint, path) 118 | log.debug('Constructed URL as: {0}'.format(url)) 119 | return url 120 | 121 | def get(self, url, params=None): 122 | return self._request(url, params=params) 123 | 124 | def post(self, url, json_payload='{}'): 125 | return self._request(url, json_payload, http_verb='POST') 126 | 127 | def put(self, url, json_payload='{}'): 128 | return self._request(url, json_payload, http_verb='PUT') 129 | 130 | def delete(self, url, params=None): 131 | return self._request(url, params=params, http_verb='DELETE') 132 | 133 | def _request(self, url, json_payload='{}', http_verb='GET', params=None): 134 | json_payload = json.dumps(json_payload) 135 | 136 | try: 137 | if http_verb == "PUT": 138 | req = self._session.put( 139 | self._construct_url(url), 140 | verify=self.verify_ssl, 141 | headers=self._fetch_headers(), 142 | timeout=self.request_timeout, 143 | data=json_payload) 144 | elif http_verb == 'POST': 145 | req = self._session.post( 146 | self._construct_url(url), 147 | verify=self.verify_ssl, 148 | headers=self._fetch_headers(), 149 | timeout=self.request_timeout, 150 | data=json_payload) 151 | elif http_verb == 'DELETE': 152 | # Need to follow up - if 'accept' is in the headers 153 | # delete calls are not working because ECS 2.0 is returning 154 | # XML even if JSON is specified 155 | headers = self._fetch_headers() 156 | del headers['Accept'] 157 | 158 | req = self._session.delete( 159 | self._construct_url(url), 160 | verify=self.verify_ssl, 161 | headers=headers, 162 | timeout=self.request_timeout, 163 | params=params) 164 | else: # Default to GET 165 | req = self._session.get( 166 | self._construct_url(url), 167 | verify=self.verify_ssl, 168 | headers=self._fetch_headers(), 169 | timeout=self.request_timeout, 170 | params=params) 171 | 172 | # Because some delete actions in the API return HTTP/1.1 204 No Content 173 | if not (200 <= req.status_code < 300): 174 | log.error("Status code NOT OK") 175 | raise ECSClientException.from_response(req) 176 | try: 177 | return req.json() 178 | except ValueError: 179 | return req.text 180 | 181 | except requests.ConnectionError as conn_err: 182 | msg = 'Connection error: {0}'.format(conn_err.args) 183 | log.error(msg) 184 | raise ECSClientException(message=msg) 185 | except requests.HTTPError as http_err: 186 | msg = 'HTTP error: {0}'.format(http_err.args) 187 | log.error(msg) 188 | raise ECSClientException(message=msg) 189 | except requests.RequestException as req_err: 190 | msg = 'Request error: {0}'.format(req_err.args) 191 | log.error(msg) 192 | raise ECSClientException(message=msg) 193 | --------------------------------------------------------------------------------