Scott Hussey 37daf1f95f Add locking around DesignState change methods to prepare
for multithreaded access

Support design and build change merging instead of just
replacing. Includes basic unit test.

Add readme for model design
2017-03-21 16:30:35 -05:00

439 lines
15 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.
from copy import deepcopy
from datetime import datetime
from datetime import timezone
from threading import Lock
import uuid
import helm_drydock.model.node as node
import helm_drydock.model.hostprofile as hostprofile
import helm_drydock.model.network as network
import helm_drydock.model.site as site
import helm_drydock.model.hwprofile as hwprofile
from helm_drydock.error import DesignError, StateError
class DesignState(object):
def __init__(self):
self.design_base = None
self.design_base_lock = Lock()
self.design_changes = []
self.design_changes_lock = Lock()
self.builds = []
self.builds_lock = Lock()
return
# TODO Need to lock a design base or change once implementation
# has started
def get_design_base(self):
if self.design_base is None:
raise DesignError("No design base submitted")
return deepcopy(self.design_base)
def post_design_base(self, site_design):
if site_design is not None and isinstance(site_design, SiteDesign):
my_lock = self.design_base_lock.acquire(blocking=True,
timeout=10)
if my_lock:
self.design_base = deepcopy(site_design)
self.design_base_lock.release()
return True
raise StateError("Could not acquire lock")
else:
raise DesignError("Design change must be a SiteDesign instance")
def put_design_base(self, site_design):
if site_design is not None and isinstance(site_design, SiteDesign):
my_lock = self.design_base_lock.acquire(blocking=True,
timeout=10)
if my_lock:
self.design_base.merge_updates(site_design)
self.design_base_lock.release()
return True
raise StateError("Could not acquire lock")
else:
raise DesignError("Design base must be a SiteDesign instance")
def get_design_change(self, changeid):
match = [x for x in self.design_changes if x.changeid == changeid]
if len(match) == 0:
raise DesignError("No design change %s found." % (changeid))
else:
return deepcopy(match[0])
def post_design_change(self, site_design):
if site_design is not None and isinstance(site_design, SiteDesign):
my_lock = self.design_changes_lock.acquire(block=True,
timeout=10)
if my_lock:
exists = [(x) for x
in self.design_changes
if x.changeid == site_design.changeid]
if len(exists) > 0:
self.design_changs_lock.release()
raise DesignError("Existing change %s found" %
(site_design.changeid))
self.design_changes.append(deepcopy(site_design))
self.design_changes_lock.release()
return True
raise StateError("Could not acquire lock")
else:
raise DesignError("Design change must be a SiteDesign instance")
def put_design_change(self, site_design):
if site_design is not None and isinstance(site_design, SiteDesign):
my_lock = self.design_changes_lock.acquire(block=True,
timeout=10)
if my_lock:
changeid = site_design.changeid
for c in self.design_changes:
if c.changeid == changeid:
c.merge_updates(site_design)
return True
raise StateError("Could not acquire lock")
else:
raise DesignError("Design change must be a SiteDesign instance")
def get_current_build(self):
latest_stamp = 0
current_build = None
for b in self.builds:
if b.build_id > latest_stamp:
latest_stamp = b.build_id
current_build = b
return deepcopy(current_build)
def get_build(self, build_id):
for b in self.builds:
if b.build_id == build_id:
return b
return None
def post_build(self, site_build):
if site_build is not None and isinstance(site_build, SiteBuild):
my_lock = self.builds_lock.acquire(block=True, timeout=10)
if my_lock:
exists = [b for b in self.builds
if b.build_id == site_build.build_id]
if len(exists) > 0:
self.builds_lock.release()
raise DesignError("Already a site build with ID %s" %
(str(site_build.build_id)))
self.builds.append(deepcopy(site_build))
self.builds_lock.release()
return True
raise StateError("Could not acquire lock")
else:
raise DesignError("Design change must be a SiteDesign instance")
def put_build(self, site_build):
if site_build is not None and isinstance(site_build, SiteBuild):
my_lock = self.builds_lock.acquire(block=True, timeout=10)
if my_lock:
buildid = site_build.buildid
for b in self.builds:
if b.buildid == buildid:
b.merge_updates(site_build)
self.builds_lock.release()
return True
self.builds_lock.release()
return False
raise StateError("Could not acquire lock")
else:
raise DesignError("Design change must be a SiteDesign instance")
class SiteDesign(object):
def __init__(self, ischange=False, changeid=None):
if ischange:
if changeid is not None:
self.changeid = changeid
else:
self.changeid = uuid.uuid4()
else:
# Base design
self.changeid = 0
self.sites = []
self.networks = []
self.network_links = []
self.host_profiles = []
self.hardware_profiles = []
self.baremetal_nodes = []
def add_site(self, new_site):
if new_site is None or not isinstance(new_site, site.Site):
raise DesignError("Invalid Site model")
self.sites.append(new_site)
def update_site(self, update):
if update is None or not isinstance(update, site.Site):
raise DesignError("Invalid Site model")
for i, s in enumerate(self.sites):
if s.get_name() == update.get_name():
self.sites[i] = deepcopy(update)
return True
return False
def get_sites(self):
return self.sites
def get_site(self, site_name):
for s in self.sites:
if s.name == site_name:
return s
raise DesignError("Site %s not found in design state" % site_name)
def add_network(self, new_network):
if new_network is None or not isinstance(new_network, network.Network):
raise DesignError("Invalid Network model")
self.networks.append(new_network)
def update_network(self, update):
if update is None or not isinstance(update, network.Network):
raise DesignError("Invalid Network model")
for i, n in enumerate(self.networks):
if n.get_name() == update.get_name():
self.networks[i] = deepcopy(update)
return True
return False
def get_networks(self):
return self.networks
def get_network(self, network_name):
for n in self.networks:
if n.name == network_name:
return n
raise DesignError("Network %s not found in design state"
% network_name)
def add_network_link(self, new_network_link):
if new_network_link is None or not isinstance(new_network_link,
network.NetworkLink):
raise DesignError("Invalid NetworkLink model")
self.network_links.append(new_network_link)
def update_network_link(self, update):
if update is None or not isinstance(update, network.NetworkLink):
raise DesignError("Invalid NetworkLink model")
for i, n in enumerate(self.network_links):
if n.get_name() == update.get_name():
self.network_links[i] = deepcopy(update)
return True
return False
def get_network_links(self):
return self.network_links
def get_network_link(self, link_name):
for l in self.network_links:
if l.name == link_name:
return l
raise DesignError("NetworkLink %s not found in design state"
% link_name)
def add_host_profile(self, new_host_profile):
if new_host_profile is None or not isinstance(new_host_profile,
hostprofile.HostProfile):
raise DesignError("Invalid HostProfile model")
self.host_profiles.append(new_host_profile)
def update_host_profile(self, update):
if update is None or not isinstance(update, hostprofile.HostProfile):
raise DesignError("Invalid HostProfile model")
for i, h in enumerate(self.host_profiles):
if h.get_name() == update.get_name():
self.host_profiles[i] = deepcopy(h)
return True
return False
def get_host_profiles(self):
return self.host_profiles
def get_host_profile(self, profile_name):
for p in self.host_profiles:
if p.name == profile_name:
return p
raise DesignError("HostProfile %s not found in design state"
% profile_name)
def add_hardware_profile(self, new_hardware_profile):
if (new_hardware_profile is None or
not isinstance(new_hardware_profile, hwprofile.HardwareProfile)):
raise DesignError("Invalid HardwareProfile model")
self.hardware_profiles.append(new_hardware_profile)
def update_hardware_profile(self, update):
if update is None or not isinstance(update, hwprofile.HardwareProfile):
raise DesignError("Invalid HardwareProfile model")
for i, h in enumerate(self.hardware_profiles):
if h.get_name() == update.get_name():
self.hardware_profiles[i] = deepcopy(h)
return True
return False
def get_hardware_profiles(self):
return self.hardware_profiles
def get_hardware_profile(self, profile_name):
for p in self.hardware_profiles:
if p.name == profile_name:
return p
raise DesignError("HardwareProfile %s not found in design state"
% profile_name)
def add_baremetal_node(self, new_baremetal_node):
if (new_baremetal_node is None or
not isinstance(new_baremetal_node, node.BaremetalNode)):
raise DesignError("Invalid BaremetalNode model")
self.baremetal_nodes.append(new_baremetal_node)
def update_baremetal_node(self, update):
if (update is None or not isinstance(update, node.BaremetalNode)):
raise DesignError("Invalid BaremetalNode model")
for i, b in enumerate(self.baremetal_nodes):
if b.get_name() == update.get_name():
self.baremetal_nodes[i] = deepcopy(b)
return True
return False
def get_baremetal_nodes(self):
return self.baremetal_nodes
def get_baremetal_node(self, node_name):
for n in self.baremetal_nodes:
if n.name == node_name:
return n
raise DesignError("BaremetalNode %s not found in design state"
% node_name)
# Only merge the design parts included in the updated site
# design. Changes are merged at the part level, not for fields
# within a design part
#
# TODO convert update_* methods to use exceptions and convert to try block
def merge_updates(self, updates):
if updates is not None and isinstance(updates, SiteDesign):
if updates.changeid == self.changeid:
for u in updates.sites:
if not self.update_site(u):
self.add_site(u)
for u in updates.networks:
if not self.update_network(u):
self.add_network(u)
for u in updates.network_links:
if not self.update_network_link(u):
self.add_network_link(u)
for u in updates.host_profiles:
if not self.update_host_profile(u):
self.add_host_profile(u)
for u in updates.hardware_profiles:
if not self.update_hardware_profile(u):
self.add_hardware_profile(u)
for u in updates.baremetal_nodes:
if not self.update_baremetal_node(u):
self.add_baremetal_node(u)
class SiteBuild(SiteDesign):
def __init__(self, build_id=None):
super(SiteBuild, self).__init__()
if build_id is None:
self.buildid = datetime.datetime.now(timezone.utc).timestamp()
else:
self.buildid = build_id
def get_filtered_nodes(self, node_filter):
effective_nodes = self.get_baremetal_nodes()
# filter by rack
rack_filter = node_filter.get('rackname', None)
if rack_filter is not None:
rack_list = rack_filter.split(',')
effective_nodes = [x
for x in effective_nodes
if x.get_rack() in rack_list]
# filter by name
name_filter = node_filter.get('nodename', None)
if name_filter is not None:
name_list = name_filter.split(',')
effective_nodes = [x
for x in effective_nodes
if x.get_name() in name_list]
# filter by tag
tag_filter = node_filter.get('tags', None)
if tag_filter is not None:
tag_list = tag_filter.split(',')
effective_nodes = [x
for x in effective_nodes
for t in tag_list
if x.has_tag(t)]
return effective_nodes
"""
Support filtering on rack name, node name or node tag
for now. Each filter can be a comma-delimited list of
values. The final result is an intersection of all the
filters
"""
def set_nodes_status(self, node_filter, status):
target_nodes = self.get_filtered_nodes(node_filter)
for n in target_nodes:
n.set_status(status)