Skip to content

Commit

Permalink
Folder creation
Browse files Browse the repository at this point in the history
- Generation of folder tree for deployment phase
- Added timing to some more things
- Cleanup, logging, and comments
  • Loading branch information
GhostofGoes committed Feb 25, 2017
1 parent 0713862 commit 5f73eb6
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 20 deletions.
7 changes: 6 additions & 1 deletion automation/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import logging
from .utils import time_execution


class Interface:
Expand Down Expand Up @@ -44,19 +45,22 @@ def __init__(self, spec):
from automation.vsphere_interface import VsphereInterface
self.interface = VsphereInterface(infrastructure, logins, spec) # Create interface
else:
logging.error("Invalid platform {}".format(infrastructure["platform"]))
logging.error("Invalid platform %s", infrastructure["platform"])
exit(1)

@time_execution
def create_masters(self):
""" Master creation phase """
logging.info("Creating Master instances for %s", self.metadata["name"])
self.interface.create_masters()

@time_execution
def deploy_environment(self):
""" Environment deployment phase """
logging.info("Deploying environment for %s", self.metadata["name"])
self.interface.deploy_environment()

@time_execution
def cleanup_masters(self, network_cleanup=False):
"""
Cleans up master instances
Expand All @@ -65,6 +69,7 @@ def cleanup_masters(self, network_cleanup=False):
logging.info("Cleaning up Master instances for %s", self.metadata["name"])
self.interface.cleanup_masters(network_cleanup=network_cleanup)

@time_execution
def cleanup_environment(self, network_cleanup=False):
"""
Cleans up a deployed environment
Expand Down
16 changes: 8 additions & 8 deletions automation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
# 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 getpass import getpass
from sys import stdout
from time import time
import logging


# From: virtual_machine_power_cycle_and_question.py in pyvmomi-community-samples
from automation.vsphere.vsphere import Vsphere


# From: virtual_machine_power_cycle_and_question.py in pyvmomi-community-samples
def _create_char_spinner():
""" Creates a generator yielding a char based spinner """
while True:
Expand All @@ -31,10 +31,10 @@ def _create_char_spinner():
_spinner = _create_char_spinner()


def spinner(label=''):
def spinner(label=""):
"""
Prints label with a spinner. When called repeatedly from inside a loop this prints a one line CLI spinner.
:param label: (Optional) The message to display while spinning (e.g "Loading", or the current percentage)
:param label: The message to display while spinning (e.g "Loading", or the current percentage) [default: ""]
"""
stdout.write("\r\t%s %s" % (label, next(_spinner)))
stdout.flush()
Expand Down Expand Up @@ -73,7 +73,7 @@ def prompt_y_n_question(question, default="no"):
elif default == "no":
prompt = " [y/N] "
else:
raise ValueError("Invalid default answer: '{}'".format(default))
raise ValueError("Invalid default answer: '%s'", default)

while True:
logging.info(question + prompt)
Expand All @@ -90,7 +90,7 @@ def pad(value, length=2):
"""
Adds leading and trailing ("pads") zeros to value to ensure it is a constant length
:param value: integer value to pad
:param length: Length to pad to
:param length: Length to pad to [default: 2]
:return: string of padded value
"""
return "{0:0>{width}}".format(value, width=length)
Expand All @@ -99,7 +99,7 @@ def pad(value, length=2):
def read_json(filename):
"""
Reads input from a JSON file and returns the contents
:param filename:
:param filename: Path to JSON file to read
:return: Contents of the JSON file
"""
from json import load
Expand Down Expand Up @@ -163,7 +163,7 @@ def wrapper(*args, **kwargs):
def make_vsphere(filename=None):
"""
Creates a vSphere object using either a JSON file or by prompting the user for input
:param filename: (Optional) Name of JSON file with information needed [default: None]
:param filename: Name of JSON file with information needed [default: None]
:return: vSphere object
"""
if filename:
Expand Down
7 changes: 3 additions & 4 deletions automation/vsphere/network_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def create_vswitch(name, host, num_ports=512):
:param host: vim.HostSystem to create the vSwitch on
:param num_ports: Number of ports the vSwitch should have [default: 512]
"""
logging.info("Creating vSwitch {} with {} ports on host {}".format(name, str(num_ports), host.name))
logging.info("Creating vSwitch %s with %s ports on host %s", name, str(num_ports), host.name)
vswitch_spec = vim.host.VirtualSwitch.Specification()
vswitch_spec.numPorts = num_ports
try:
Expand All @@ -40,14 +40,13 @@ def delete_vswitch(name, host):
:param name: Name of the vSwitch to delete
:param host: vim.HostSystem to delete vSwitch from
"""
logging.info("Deleting vSwitch {} from host {}".format(name, host.name))
logging.info("Deleting vSwitch %s from host %s", name, host.name)
try:
host.configManager.networkSystem.RemoveVirtualSwitch(name)
except vim.fault.NotFound:
logging.error("Tried to remove a vSwitch %s that does not exist from host %s", name, host.name)
except vim.fault.ResourceInUse:
logging.error("vSwitch %s can't be removed because there are "
"virtual network adapters associated with it", name)
logging.error("vSwitch %s can't be removed because there are virtual network adapters associated with it", name)


def create_portgroup(name, host, vswitch_name, vlan=0, promiscuous=False):
Expand Down
2 changes: 1 addition & 1 deletion automation/vsphere/vm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def remove_device(vm, device):
:param vm: vim.VirtualMachine
:param device: vim.vm.device.VirtualDeviceSpec
"""
logging.debug("Removing device {} from vm {}".format(device.name, vm.name))
logging.debug("Removing device %s from vm %s", device.name, vm.name)
device.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove
wait_for_task(vm.ReconfigVM_Task(vim.vm.ConfigSpec(deviceChange=[device]))) # Apply the change to the VM

Expand Down
2 changes: 1 addition & 1 deletion automation/vsphere/vsphere_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def cleanup(folder, prefix=None, recursive=False, destroy_folders=False, destroy
"""
if folder: # Checks to make sure folder is not None
from automation.vsphere.vm_utils import destroy_vm
logging.info("Cleaning folder %s", folder.name)
logging.debug("Cleaning folder %s", folder.name)
for item in folder.childEntity:
if is_vm(item): # Handle VMs
if prefix:
Expand Down
78 changes: 74 additions & 4 deletions automation/vsphere_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from automation.vsphere.network_utils import *
from automation.vsphere.vsphere_utils import *
from automation.vsphere.vm_utils import *
from automation.utils import pad


class VsphereInterface:
Expand All @@ -26,7 +27,7 @@ class VsphereInterface:
# Switches to tweak (these are global to ALL instances of this class)
master_prefix = "(MASTER) "
master_folder_name = "MASTER_FOLDERS"
warn_threshold = 100 # Point at which to warn if many instances are being created
warn_threshold = 100 # Point at which to warn if many instances are being created

def __init__(self, infrastructure, logins, spec):
logging.debug("Initializing VsphereInterface...")
Expand Down Expand Up @@ -115,6 +116,7 @@ def create_masters(self):
set_note(vm=new_vm, note=service_config["note"])

# Post-creation snapshot
logging.debug("Creating post-clone snapshot")
create_snapshot(new_vm, "post-clone", "Clean snapshot taken after cloning and configuration.")

# Apply master-group permissions [default: group permissions]
Expand All @@ -128,13 +130,15 @@ def _create_master_networks(self, net_type):
vlan = config["vlan"]
else:
vlan = 0
logging.debug("Creating portgroup %s", name)
create_portgroup(name, host, config["vswitch"], vlan=vlan)

def deploy_environment(self):
""" Environment deployment phase """

# Get the master folder root (TODO: sub-masters or multiple masters?)
master_folder = traverse_path(self.root_folder, self.master_folder_name)
logging.debug("Master folder name: %s\tPrefix: %s", master_folder.name, self.master_prefix)

# Verify and convert to templates.
# This is to ensure they are preserved throughout creation and execution of exercise.
Expand All @@ -143,12 +147,13 @@ def deploy_environment(self):
if "template" in service_config:
vm = traverse_path(master_folder, self.master_prefix + service_name)
if vm: # Verify all masters exist
logging.debug("Verified master %s exists. Name of VM: %s. Converting to template...",
service_name, vm.name)
logging.debug("Verified master %s exists as %s. Converting to template...", service_name, vm.name)
convert_to_template(vm) # Convert master to template
logging.debug("Converted master %s to template. Verifying...", service_name)
if not is_template(vm): # Verify converted successfully
logging.error("Master %s did not convert to template!", service_name)
else:
logging.debug("Verified!")
else:
logging.error("Could not find master %s", service_name)

Expand All @@ -159,13 +164,15 @@ def deploy_environment(self):
# Create base-networks

# Create folder structure

# if "services" in folder: then normal-folder
# else: parent-folder
# So, need to iterate and create folders.
# if "instances" in folder: then create range(instances) folder.name + pad + prefix <-- Optional prefix
# ^ resolve "size-of" if specified instead of "number"
# else: create folder
# Creating functions for normal and parent folders that can call each other
# Need to also figure out when/how to apply permissions
self._folder_gen(self.folders, self.root_folder)

# Enumerate folder tree to debugging
logging.debug(format_structure(enumerate_folder(self.root_folder)))
Expand All @@ -177,6 +184,69 @@ def deploy_environment(self):
# Enumerate tree with VMs to debugging
logging.debug(format_structure(enumerate_folder(self.root_folder)))

def _normal_folder_gen(self, folder, spec):
for key, value in spec:
if key == "instances" or key == "master-group":
pass
elif key == "group":
pass # TODO: apply group permissions
elif key == "description":
pass # TODO: ?
elif key == "services":
pass # TODO: services?
else:
logging.error("Unknown key in normal-type folder %s: %s", folder.name, key)

def _parent_folder_gen(self, folder, spec):
for sub_name, sub_value in spec.items():
if sub_name == "instances":
pass
elif sub_name == "group":
pass # TODO: apply group permissions
else:
self._folder_gen(sub_value, folder)

def _folder_gen(self, folders, parent):
for name, value in folders.items():
num_instances = self._instances_handler(value["instances"])
logging.debug("Generating folder %s", name)
for instance in range(num_instances):
instance_name = name + (" " + pad(instance) if num_instances > 1 else "")
folder = self.server.create_folder(folder_name=instance_name, create_in=parent)
if "services" in value: # It's a base folder
logging.debug("Generating base-type folder %s", instance_name)
self._normal_folder_gen(folder, value)
else: # It's a parent folder
logging.debug("Generating parent-type folder %s", instance_name)
self._parent_folder_gen(folder, value)

def _instances_handler(self, instances):
"""
Determines number of instances in accordance with the specification
:param instances:
:return: number of instances
"""
# TODO: interface_utils file possibly?
if "instances" in instances:
if "number" in instances["instances"]:
return int(instances["instances"]["number"])
elif "size-of" in instances["instances"]:
return int(self._group_size(self.groups[instances["instances"]["size-of"]]))
else:
logging.error("Unknown instances specification")
return 0
else:
return 1 # Default value

def _group_size(self, group):
"""
Determines number of individuals in a group
:param group:
:return: int
"""
# TODO: move into parent class? or utils?
return 1 # TODO: IMPLEMENT

def cleanup_masters(self, network_cleanup=False):
""" Cleans up any master instances"""

Expand Down
5 changes: 4 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@

from automation.interface import Interface
from automation.parser import parse_file, verify_syntax
from automation.utils import time_execution

__version__ = "0.6.0"
__version__ = "0.6.1"
__author__ = "Christopher Goes"
__email__ = "<[email protected]>"


@time_execution
def main():
if args["--spec"]:
spec = check_syntax(args["--spec"])
Expand Down Expand Up @@ -85,6 +87,7 @@ def main():
logging.error("Invalid arguments. Argument dump:\n%s", str(args))


@time_execution
def check_syntax(specfile_path):
"""
Checks the syntax of a specification file
Expand Down

0 comments on commit 5f73eb6

Please sign in to comment.