drydock/python/drydock_provisioner/drivers/node/maasdriver/models/base.py

312 lines
8.4 KiB
Python

# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# 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.
"""A representation of a MaaS REST resource.
Should be subclassed for different resources and
augmented with operations specific to those resources
"""
import json
import re
import logging
import drydock_provisioner.error as errors
class ResourceBase(object):
resource_url = '/{id}'
fields = ['resource_id']
json_fields = ['resource_id']
def __init__(self, api_client, **kwargs):
self.api_client = api_client
self.logger = logging.getLogger('drydock.nodedriver.maasdriver')
for f in self.fields:
if f in kwargs.keys():
setattr(self, f, kwargs.get(f))
"""
Update resource attributes from MaaS
"""
def refresh(self):
url = self.interpolate_url()
resp = self.api_client.get(url)
updated_fields = resp.json()
updated_model = self.from_dict(self.api_client, updated_fields)
for f in self.fields:
if hasattr(updated_model, f):
setattr(self, f, getattr(updated_model, f))
def delete(self):
"""Delete this resource in MaaS."""
url = self.interpolate_url()
self.api_client.delete(url)
"""
Parse URL for placeholders and replace them with current
instance values
"""
def interpolate_url(self):
pattern = '\{([a-z_]+)\}'
regex = re.compile(pattern)
start = 0
new_url = self.resource_url
while (start + 1) < len(self.resource_url):
match = regex.search(self.resource_url, start)
if match is None:
return new_url
param = match.group(1)
val = getattr(self, param, None)
if val is None:
raise ValueError("Missing variable value")
new_url = new_url.replace('{' + param + '}', str(val))
start = match.end(1) + 1
return new_url
"""
Update MaaS with current resource attributes
"""
def update(self):
data_dict = self.to_dict()
url = self.interpolate_url()
resp = self.api_client.put(url, files=data_dict)
if resp.status_code == 200:
return True
raise errors.DriverError(
"Failed updating MAAS url %s - return code %s\n%s" %
(url, resp.status_code, resp.text))
"""
Set the resource_id for this instance
Should only be called when creating new instances and MAAS has assigned
an id
"""
def set_resource_id(self, res_id):
self.resource_id = res_id
"""
Serialize this resource instance into JSON matching the
MaaS respresentation of this resource
"""
def to_json(self):
return json.dumps(self.to_dict())
"""
Serialize this resource instance into a dict matching the
MAAS representation of the resource
"""
def to_dict(self):
data_dict = {}
for f in self.json_fields:
if getattr(self, f, None) is not None:
if f == 'resource_id':
data_dict['id'] = getattr(self, f)
else:
data_dict[f] = getattr(self, f)
return data_dict
"""
Create a instance of this resource class based on the MaaS
representation of this resource type
"""
@classmethod
def from_json(cls, api_client, json_string):
parsed = json.loads(json_string)
if isinstance(parsed, dict):
return cls.from_dict(api_client, parsed)
raise errors.DriverError("Invalid JSON for class %s" % (cls.__name__))
"""
Create a instance of this resource class based on a dict
of MaaS type attributes
"""
@classmethod
def from_dict(cls, api_client, obj_dict):
refined_dict = {k: obj_dict.get(k, None) for k in cls.fields}
if 'id' in obj_dict.keys():
refined_dict['resource_id'] = obj_dict.get('id')
i = cls(api_client, **refined_dict)
return i
class ResourceCollectionBase(object):
"""A collection of MaaS resources.
Rather than a simple list, we will key the collection on resource
ID for more efficient access.
:param api_client: An instance of api_client.MaasRequestFactory
"""
collection_url = ''
collection_resource = ResourceBase
def __init__(self, api_client):
self.api_client = api_client
self.resources = {}
self.logger = logging.getLogger('drydock.nodedriver.maasdriver')
def interpolate_url(self):
"""Parse URL for placeholders and replace them with current instance values."""
pattern = '\{([a-z_]+)\}'
regex = re.compile(pattern)
start = 0
new_url = self.collection_url
while (start + 1) < len(self.collection_url):
match = regex.search(self.collection_url, start)
if match is None:
return new_url
param = match.group(1)
val = getattr(self, param, None)
if val is None:
raise ValueError("Missing variable value")
new_url = new_url.replace('{' + param + '}', str(val))
start = match.end(1) + 1
return new_url
"""
Create a new resource in this collection in MaaS
"""
def add(self, res):
data_dict = res.to_dict()
url = self.interpolate_url()
resp = self.api_client.post(url, files=data_dict)
if resp.status_code in [200, 201]:
resp_json = resp.json()
res.set_resource_id(resp_json.get('id'))
return res
raise errors.DriverError("Failed updating MAAS url %s - return code %s"
% (url, resp.status_code))
"""
Append a resource instance to the list locally only
"""
def append(self, res):
if isinstance(res, self.collection_resource):
self.resources[res.resource_id] = res
"""
Initialize or refresh the collection list from MaaS
"""
def refresh(self):
url = self.interpolate_url()
resp = self.api_client.get(url)
if resp.status_code == 200:
self.resource = {}
json_list = resp.json()
for o in json_list:
if isinstance(o, dict):
i = self.collection_resource.from_dict(self.api_client, o)
self.resources[i.resource_id] = i
return
"""
Check if resource id is in this collection
"""
def contains(self, res_id):
if res_id in self.resources.keys():
return True
return False
"""
Select a resource based on ID or None if not found
"""
def select(self, res_id):
return self.resources.get(res_id, None)
"""
Query the collection based on a resource attribute other than primary id
"""
def query(self, query):
result = list(self.resources.values())
for (k, v) in query.items():
result = [i for i in result if str(getattr(i, k, None)) == str(v)]
return result
def singleton(self, query):
"""A query that requires a single item response.
:param query: A dict of k:v pairs defining the query parameters
"""
result = self.query(query)
if len(result) > 1:
raise ValueError("Multiple results found")
elif len(result) == 1:
return result[0]
return None
"""
If the collection contains a single item, return it
"""
def single(self):
if self.len() == 1:
for v in self.resources.values():
return v
else:
return None
def __iter__(self):
"""Iterate over the resources in the collection."""
return iter(self.resources.values())
def len(self):
return len(self)
def __len__(self):
"""Resource count."""
return len(self.resources)