Neutron Jobs API for SG auto port update checks

- Adding the response model
- Adding the client and behaviors for GET calls
- Adding the composites and constants

Change-Id: I8d6c962b5ed8ddbe0181f32283b8de657db9f4c1
This commit is contained in:
Leonardo Maycotte 2016-10-07 22:20:58 -05:00
parent de93ab7732
commit 7a13551cac
7 changed files with 429 additions and 0 deletions

View File

@ -0,0 +1,15 @@
"""
Copyright 2016 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

View File

@ -0,0 +1,106 @@
"""
Copyright 2016 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from cloudcafe.networking.networks.common.behaviors \
import NetworkingBaseBehaviors
from cloudcafe.networking.networks.extensions.jobs_api.constants \
import JobsResource, JobsResponseCodes
class JobsBehaviors(NetworkingBaseBehaviors):
def __init__(self, jobs_client):
super(JobsBehaviors, self).__init__()
self.client = jobs_client
self.response_codes = JobsResponseCodes
self.jobs_resource = JobsResource(JobsResource.JOB)
def get_job(self, job_id, resource_get_attempts=None,
raise_exception=False, poll_interval=None):
"""Get job by ID and verifies response
Args:
job_id (str): job ID
resource_get_attempts (Optional[int]): number of API retries
raise_exception (Optional[bool]): flag to raise an exception
if the GET call response is unexpected (default set to False).
poll_interval (Optional[int]): sleep time interval between API
retries
Returns:
common.behaviors.NetworkingResponse object with API response and
failure list with failures if any.
"""
result = self._get_resource(
resource=self.jobs_resource, resource_id=job_id,
resource_get_attempts=resource_get_attempts,
raise_exception=raise_exception, poll_interval=poll_interval)
return result
def list_jobs(self, job_id=None, tenant_id=None, transaction_id=None,
parent_id=None, subtransactions=None,
completed_subtransactions=None, transaction_percent=None,
completed=None, action=None, created_at=None, limit=None,
marker=None, page_reverse=None,
resource_list_attempts=None, raise_exception=False,
poll_interval=None):
"""List jobs and verifies the response
Args:
job_id (Optional[str]): get job with this ID.
tenant_id (Optional[str]): get jobs for this tenant ID.
transaction_id (Optional[str]): get jobs with this transaction ID.
parent_id (Optional[str]): get jobs with this parent ID.
subtransactions (Optional[int]): get jobs with this number of
subtransactions.
completed_subtransactions (Optional[int]): get jobs with this
number of completed subtransactions.
transaction_percent (Optional[int]): get jobs with this completion
percentage.
completed (Optional[bool]): get jobs with this true or false flag.
action (Optional[str]): get jobs with this action,
for ex. create sg rule <sg_rule_id> or update port <port_id>.
created_at (Optional[str]): get jobs with this creation date.
limit (Optional[int]): page size.
marker (Optional[str]): ID of the last item of the previous page.
page_reverse (Optional[bool]): direction of the page.
resource_list_attempts (Optional[int]): number of API retries
raise_exception (Optional[bool]): flag to raise an exception
if the GET call response is unexpected (default set to False).
poll_interval (Optional[int]): sleep time interval between API
retries
Returns:
common.behaviors.NetworkingResponse object with API response and
failure list with failures if any.
"""
params_kwargs = dict(
job_id=job_id, tenant_id=tenant_id, transaction_id=transaction_id,
parent_id=parent_id, subtransactions=subtransactions,
completed_subtransactions=completed_subtransactions,
transaction_percent=transaction_percent, completed=completed,
action=action, created_at=created_at, limit=limit, marker=marker,
page_reverse=page_reverse)
result = self._list_resources(
resource=self.jobs_resource,
resource_list_attempts=resource_list_attempts,
raise_exception=raise_exception, poll_interval=poll_interval,
params_kwargs=params_kwargs)
return result

View File

@ -0,0 +1,112 @@
"""
Copyright 2016 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from cafe.engine.http.client import AutoMarshallingHTTPClient
from cloudcafe.networking.networks.extensions.jobs_api.models.response \
import Job, Jobs
class JobsClient(AutoMarshallingHTTPClient):
"""Networking jobs client for GET calls.
Attributes:
url (str): Base URL for the networks service.
auth_token (str): Auth token to be used for all requests.
serialize_format (str): Format for serializing requests.
deserialize_format (str): Format for de-serializing responses.
tenant_id (Optional[str]): tenant id to be included in the header
if given.
"""
def __init__(self, url, auth_token, serialize_format=None,
deserialize_format=None, tenant_id=None):
super(JobsClient, self).__init__(serialize_format, deserialize_format)
self.auth_token = auth_token
self.default_headers['X-Auth-Token'] = auth_token
ct = '{content_type}/{content_subtype}'.format(
content_type='application', content_subtype=self.serialize_format)
accept = '{content_type}/{content_subtype}'.format(
content_type='application',
content_subtype=self.deserialize_format)
self.default_headers['Content-Type'] = ct
self.default_headers['Accept'] = accept
if tenant_id:
self.default_headers['X-Auth-Project-Id'] = tenant_id
self.jobs_url = '{url}/jobs'.format(url=url)
def get_job(self, job_id, requestslib_kwargs=None):
"""Get job by ID
Args:
job_id (str): job ID
Returns:
Requests.resonse object from the GET job with ID API call.
"""
url = '{base_url}/{job_id}'.format(base_url=self.jobs_url,
job_id=job_id)
resp = self.request('GET', url, response_entity_type=Job,
requestslib_kwargs=requestslib_kwargs)
return resp
def list_jobs(self, job_id=None, tenant_id=None, transaction_id=None,
parent_id=None, subtransactions=None,
completed_subtransactions=None, transaction_percent=None,
completed=None, action=None, created_at=None, limit=None,
marker=None, page_reverse=None, requestslib_kwargs=None):
"""List jobs
Args:
job_id (Optional[str]): get job with this ID.
tenant_id (Optional[str]): get jobs for this tenant ID.
transaction_id (Optional[str]): get jobs with this transaction ID.
parent_id (Optional[str]): get jobs with this parent ID.
subtransactions (Optional[int]): get jobs with this number of
subtransactions.
completed_subtransactions (Optional[int]): get jobs with this
number of completed subtransactions.
transaction_percent (Optional[int]): get jobs with this completion
percentage.
completed (Optional[bool]): get jobs with this true or false flag.
action (Optional[str]): get jobs with this action,
for ex. create sg rule <sg_rule_id> or update port <port_id>.
created_at (Optional[str]): get jobs with this creation date.
limit (Optional[int]): page size.
marker (Optional[str]): ID of the last item of the previous page.
page_reverse (Optional[bool]): direction of the page.
Returns:
List of Requests.resonse objects from the GET jobs API call.
The list may be filtered if optional args are given.
"""
params = {'id': job_id, 'tenant_id': tenant_id,
'transaction_id': transaction_id, 'parent_id': parent_id,
'subtransactions': subtransactions,
'completed_subtransactions': completed_subtransactions,
'transaction_percent': transaction_percent,
'completed': completed, 'action': action,
'created_at': created_at, 'limit': limit, 'marker': marker,
'page_reverse': page_reverse}
url = self.jobs_url
resp = self.request('GET', url, params=params,
response_entity_type=Jobs,
requestslib_kwargs=requestslib_kwargs)
return resp

View File

@ -0,0 +1,33 @@
"""
Copyright 2016 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from cloudcafe.networking.networks.composites import _NetworkingAuthComposite
from cloudcafe.networking.networks.extensions.jobs_api.behaviors \
import JobsBehaviors
from cloudcafe.networking.networks.extensions.jobs_api.client \
import JobsClient
class JobsComposite(object):
networking_auth_composite = _NetworkingAuthComposite
def __init__(self, auth_composite=None):
auth_composite = auth_composite or self.networking_auth_composite()
self.url = auth_composite.networking_url
self.user = auth_composite._auth_user_config
self.client = JobsClient(**auth_composite.client_args)
self.behaviors = JobsBehaviors(jobs_client=self.client)

View File

@ -0,0 +1,41 @@
"""
Copyright 2016 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from cloudcafe.networking.networks.common.constants \
import NeutronResource, NeutronResponseCodes, NeutronErrorTypes
class JobsResource(NeutronResource):
"""Jobs resource types"""
# Resources to be used by the behavior
JOB = 'job'
JOBS = 'jobs'
PLURALS = NeutronResource.PLURALS
PLURALS.update({JOB: JOBS})
class JobsResponseCodes(NeutronResponseCodes):
"""HTTP Jobs API Response codes"""
GET_JOB = 200
LIST_JOBS = 200
class JobsErrorTypes(NeutronErrorTypes):
"""Jobs Error Types"""
JOB_NOT_FOUND = 'JobNotFound'

View File

@ -0,0 +1,15 @@
"""
Copyright 2016 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

View File

@ -0,0 +1,107 @@
"""
Copyright 2016 Rackspace
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import copy
import json
from cafe.engine.models.base import AutoMarshallingListModel, \
AutoMarshallingModel
class Job(AutoMarshallingModel):
"""Networking Job model object to track the auto port update transactions
done by adding and deleting security group rules
Attributes:
id (str): job ID.
tenant_id (str): user tenant ID.
transaction_id (str): multiple jobs can belong to one transaction.
subtransactions and parents will have the same transaction_id.
parent_id (str): parent ID (may not always be used and set to None).
subtransactions (int): number of child jobs.
completed_subtransactions (int): number of subtransactions completed.
transaction_percent (int): job completion percentage (100 for done).
completed (bool): set to true when the job is done.
action (str): job description, for ex. create sg rule <sg_rule_id>
or update port <port_id>.
created_at (str): creation date, for ex. 2016-10-07 16:16:00.
"""
JOB = 'job'
def __init__(self, id_=None, tenant_id=None, transaction_id=None,
parent_id=None, subtransactions=None,
completed_subtransactions=None, transaction_percent=None,
completed=None, action=None, created_at=None, **kwargs):
# kwargs is to be used for extensions or checking unexpected attrs
super(Job, self).__init__()
self.id = id_
self.tenant_id = tenant_id
self.transaction_id = transaction_id
self.parent_id = parent_id
self.subtransactions = subtransactions
self.completed_subtransactions = completed_subtransactions
self.transaction_percent = transaction_percent
self.completed = completed
self.action = action
self.created_at = created_at
self.kwargs = kwargs
@classmethod
def _json_to_obj(cls, serialized_str):
"""Return job object from a JSON serialized string"""
ret = None
json_response = json.loads(serialized_str)
# Creating a deep copy just in case later we want the original resp
json_dict = copy.deepcopy(json_response)
# Replacing attribute response names if they are Python reserved words
# with a trailing underscore, for ex. id for id_
json_dict = cls._replace_dict_key(
json_dict, 'id', 'id_', recursion=True)
if cls.JOB in json_dict:
job_dict = json_dict.get(cls.JOB)
ret = Job(**job_dict)
return ret
class Jobs(AutoMarshallingListModel):
JOBS = 'jobs'
@classmethod
def _json_to_obj(cls, serialized_str):
"""Return a list of job objects from a JSON serialized string"""
ret = cls()
json_response = json.loads(serialized_str)
# Creating a deep copy just in case later we want the original resp
json_dict = copy.deepcopy(json_response)
# Replacing attribute response names if they are Python reserved words
# with a trailing underscore, for ex. id for id_
json_dict = cls._replace_dict_key(
json_dict, 'id', 'id_', recursion=True)
if cls.JOBS in json_dict:
jobs = json_dict.get(cls.JOBS)
for job in jobs:
ret.append(Job(**job))
return ret