Skip to content
This repository has been archived by the owner on Aug 30, 2024. It is now read-only.

Custom userdata tests #233

Open
wants to merge 5 commits into
base: testing
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions eucaops/ec2ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -2389,6 +2389,7 @@ def run_instance(self,
min=1,
max=1,
user_data=None,
user_data_file=None,
private_addressing=False,
username="root",
password=None,
Expand All @@ -2406,6 +2407,7 @@ def run_instance(self,
:param min: Minimum instnaces to launch, default 1
:param max: Maxiumum instances to launch, default 1
:param user_data: User-data string to pass to instance
:param user_data_file: User-data file to pass to instance
:param private_addressing: Runs an instance with only private IP address
:param username: username to use when connecting via ssh
:param password: password to use when connecting via ssh
Expand All @@ -2420,6 +2422,9 @@ def run_instance(self,
image = self.get_emi(emi=str(image))
if image is None:
raise Exception("emi is None. run_instance could not auto find an emi?")
if user_data_file:
with open(user_data_file) as userdata_file:
user_data = userdata_file.read()
if not user_data:
user_data = self.enable_root_user_data
if private_addressing is True:
Expand Down Expand Up @@ -2512,6 +2517,7 @@ def run_image(self,
max=1,
block_device_map=None,
user_data=None,
user_data_file=None,
private_addressing=False,
username="root",
password=None,
Expand All @@ -2529,6 +2535,7 @@ def run_image(self,
:param min: minimum amount of instances to try to run
:param max: max amount of instances to try to run
:param user_data: user_data to run instances with
:param user_data_file: user_data file to run instances with
:param private_addressing: boolean to run instances without public ips
:param username: username for connecting ssh to instances
:param password: password for connnecting ssh to instances
Expand All @@ -2551,6 +2558,9 @@ def run_image(self,
image = self.get_emi(emi=str(image))
if image is None:
raise Exception("emi is None. run_instance could not auto find an emi?")
if user_data_file:
with open(user_data_file) as userdata_file:
user_data = userdata_file.read()
if not user_data:
user_data = self.enable_root_user_data
if private_addressing is True:
Expand Down
5 changes: 5 additions & 0 deletions eutester/euinstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,11 @@ def get_metadata(self, element_path, prefix='latest/meta-data/', timeout=10, sta
return self.sys("curl http://" + self.tester.get_ec2_ip() + ":8773/"+str(prefix) + str(element_path), code=0)
else:
raise(se)

def get_userdata(self, prefix='latest/user-data/'):
"""Return the userdata"""
element_path = ""
return self.get_metadata(element_path, prefix)

def set_block_device_prefix(self):
return self.set_rootfs_device()
Expand Down
3 changes: 3 additions & 0 deletions eutester/eutestcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,9 @@ def setup_parser(self,
if userdata:
parser.add_argument('--user-data',
help="User data string to provide instance run within this test", default=None)
if userdata:
parser.add_argument('--user-data-file',
help="User data file to provide instance run within this test", default=None)
if instance_user:
parser.add_argument('--instance-user',
help="Username used for ssh login. Default:'root'", default='root')
Expand Down
33 changes: 29 additions & 4 deletions testcases/cloud_user/instances/instancetest.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@ def __init__( self, name="InstanceBasics", credpath=None, region=None, config_fi
self.address = None
self.volume = None
self.private_addressing = False
if not user_data:
### Set userdata string to 16K to test max string size for userdata
self.user_data = self.tester.id_generator(16000)
if not zone:
zones = self.tester.ec2.get_all_zones()
self.zone = random.choice(zones).name
else:
self.zone = zone
self.reservation = None
self.reservation_lock = threading.Lock()
self.run_instance_params = {'image': self.image, 'user_data': user_data, 'username': instance_user,
'keypair': self.keypair.name, 'group': self.group.name, 'zone': self.zone,
'timeout': self.instance_timeout}
self.run_instance_params = {'image': self.image, 'user_data': self.user_data, 'username': instance_user,
'keypair': self.keypair.name, 'group': self.group.name,
'zone': self.zone, 'timeout': self.instance_timeout}
self.managed_network = True

### If I have access to the underlying infrastructure I can look
Expand Down Expand Up @@ -238,6 +241,28 @@ def MetaData(self):
self.set_reservation(reservation)
return reservation

def UserData(self):
"""
This case was developed to test the user-data service of an instance for consistency.
This case does a comparison of the user data passed in by the user-data argument to
the data supplied by the user-data service within the instance. Supported
user data formats can be found here: https://cloudinit.readthedocs.org/en/latest/topics/format.html
If this test fails, the test case will error out; logging the results.
The userdata tested is 16K string (maximum size of userdata string defined by AWS)
"""
if not self.reservation:
reservation = self.tester.run_instance(**self.run_instance_params)
else:
reservation = self.reservation
for instance in reservation.instances:
"""
For aesthetics, the user data value is a 16K file thats converted to string then compare,
"""
if self.user_data:
self.assertEqual(instance.get_userdata()[0], self.user_data, 'Incorrect User Data String')
self.set_reservation(reservation)
return reservation

def DNSResolveCheck(self):
"""
This case was developed to test DNS resolution information for public/private DNS
Expand Down Expand Up @@ -419,7 +444,7 @@ def ReuseAddresses(self):

### Either use the list of tests passed from config/command line to determine what subset of tests to run
test_list = testcase.args.tests or ["BasicInstanceChecks", "DNSResolveCheck", "Reboot", "MetaData", "ElasticIps",
"MultipleInstances", "LargestInstance", "PrivateIPAddressing", "Churn"]
"UserData", "MultipleInstances", "LargestInstance", "PrivateIPAddressing", "Churn"]
### Convert test suite methods to EutesterUnitTest objects
unit_list = []
for test in test_list:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#cloud-config
disable_root: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include-once
# entries are one url per line. comment lines beginning with '#' are allowed
# urls are passed to urllib.urlopen, so the format must be supported there
# This entries will just be processed ONE TIME by cloud-init, any further
# iterations won't process this file
http://www.ubuntu.com/robots.txt
http://www.w3schools.com/html/lastpage.htm
5 changes: 5 additions & 0 deletions testcases/cloud_user/instances/user-data-tests/include.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include
# entries are one url per line. comment lines beginning with '#' are allowed
# urls are passed to urllib.urlopen, so the format must be supported there
http://www.ubuntu.com/robots.txt
http://www.w3schools.com/html/lastpage.htm
38 changes: 38 additions & 0 deletions testcases/cloud_user/instances/user-data-tests/part-handler-v2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#part-handler
# vi: syntax=python ts=4
# this is an example of a version 2 part handler.
# the differences between the initial part-handler version
# and v2 is:
# * handle_part receives a 5th argument, 'frequency'
# frequency will be either 'always' or 'per-instance'
# * handler_version must be set
#
# A handler declaring version 2 will be called on all instance boots, with a
# different 'frequency' argument.

handler_version = 2

def list_types():
# return a list of mime-types that are handled by this module
return(["text/plain", "text/go-cubs-go"])

def handle_part(data,ctype,filename,payload,frequency):
# data: the cloudinit object
# ctype: '__begin__', '__end__', or the specific mime-type of the part
# filename: the filename for the part, or dynamically generated part if
# no filename is given attribute is present
# payload: the content of the part (empty for begin or end)
# frequency: the frequency that this cloud-init run is running for
# this is either 'per-instance' or 'always'. 'per-instance'
# will be invoked only on the first boot. 'always' will
# will be called on subsequent boots.
if ctype == "__begin__":
print "my handler is beginning, frequency=%s" % frequency
return
if ctype == "__end__":
print "my handler is ending, frequency=%s" % frequency
return

print "==== received ctype=%s filename=%s ====" % (ctype,filename)
print payload
print "==== end ctype=%s filename=%s" % (ctype, filename)
23 changes: 23 additions & 0 deletions testcases/cloud_user/instances/user-data-tests/part-handler.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#part-handler
# vi: syntax=python ts=4

def list_types():
# return a list of mime-types that are handled by this module
return(["text/plain", "text/go-cubs-go"])

def handle_part(data,ctype,filename,payload):
# data: the cloudinit object
# ctype: '__begin__', '__end__', or the specific mime-type of the part
# filename: the filename for the part, or dynamically generated part if
# no filename is given attribute is present
# payload: the content of the part (empty for begin or end)
if ctype == "__begin__":
print "my handler is beginning"
return
if ctype == "__end__":
print "my handler is ending"
return

print "==== received ctype=%s filename=%s ====" % (ctype,filename)
print payload
print "==== end ctype=%s filename=%s" % (ctype, filename)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#ignored
Nothing will be done with this part by the UserDataHandler
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#upstart-job
description "My test job"

start on cloud-config
console output
task

script
echo "====BEGIN======="
echo "HELLO WORLD: $UPSTART_JOB"
echo "=====END========"
end script
12 changes: 12 additions & 0 deletions testcases/cloud_user/instances/user-data-tests/upstart-rclocal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#upstart-job
description "a test upstart job"

start on stopped rc RUNLEVEL=[2345]
console output
task

script
echo "====BEGIN======="
echo "HELLO RC.LOCAL LIKE WORLD: $UPSTART_JOB"
echo "=====END========"
end script
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

cat <<EOF
============================
My name is ${0}
I was input via user data
============================
EOF
Binary file not shown.
137 changes: 137 additions & 0 deletions testcases/cloud_user/instances/userdatatest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python
#
#
# Description: This script encompasses test cases/modules concerning instance specific behavior
# regarding userdata. The test cases/modules that are executed can be
# found in the script under the "tests" list.

import time
from concurrent.futures import ThreadPoolExecutor
import threading
from eucaops import Eucaops
from eutester.euinstance import EuInstance
from eutester.eutestcase import EutesterTestCase
from eucaops import EC2ops
import os
import re
import random
import StringIO
import difflib


class InstanceBasics(EutesterTestCase):
def __init__( self, name="InstanceBasics", credpath=None, region=None, config_file=None, password=None, emi=None, zone=None,
user_data=None, user_data_file=None, instance_user=None, **kwargs):
"""
EC2 API tests focused on instance store instances

:param credpath: Path to directory containing eucarc file
:param region: EC2 Region to run testcase in
:param config_file: Configuration file path
:param password: SSH password for bare metal machines if config is passed and keys arent synced
:param emi: Image id to use for test
:param zone: Availability Zone to run test in
:param user_data: User Data to pass to instance
:param user_data_file: User Data file to pass to instance
:param instance_user: User to login to instance as
:param kwargs: Additional arguments
"""
super(InstanceBasics, self).__init__(name=name)
if region:
self.tester = EC2ops(credpath=credpath, region=region)
else:
self.tester = Eucaops(config_file=config_file, password=password, credpath=credpath)
self.instance_timeout = 480

### Add and authorize a group for the instance
self.group = self.tester.add_group(group_name="group-" + str(time.time()))
self.tester.authorize_group_by_name(group_name=self.group.name)
self.tester.authorize_group_by_name(group_name=self.group.name, port=-1, protocol="icmp" )
### Generate a keypair for the instance
self.keypair = self.tester.add_keypair( "keypair-" + str(time.time()))
self.keypath = '%s/%s.pem' % (os.curdir, self.keypair.name)
if emi:
self.image = emi
else:
self.image = self.tester.get_emi(root_device_type="instance-store",not_location="loadbalancer")
if user_data_file:
self.user_data_file = user_data_file
self.user_data = None
elif user_data:
self.user_data = user_data
self.user_data_file = None

self.address = None
self.volume = None
self.private_addressing = False
if not zone:
zones = self.tester.ec2.get_all_zones()
self.zone = random.choice(zones).name
else:
self.zone = zone
self.reservation = None
self.reservation_lock = threading.Lock()
self.run_instance_params = {'image': self.image, 'user_data': self.user_data, 'user_data_file': self.user_data_file,
'username': instance_user, 'keypair': self.keypair.name, 'group': self.group.name,'zone': self.zone,
'timeout': self.instance_timeout}
self.managed_network = True

### If I have access to the underlying infrastructure I can look
### at the network mode and only run certain tests where it makes sense
if hasattr(self.tester,"service_manager"):
cc = self.tester.get_component_machines("cc")[0]
network_mode = cc.sys("cat " + self.tester.eucapath + "/etc/eucalyptus/eucalyptus.conf | grep MODE")[0]
if re.search("(SYSTEM|STATIC)", network_mode):
self.managed_network = False

def set_reservation(self, reservation):
self.reservation_lock.acquire()
self.reservation = reservation
self.reservation_lock.release()

def clean_method(self):
self.tester.cleanup_artifacts()

def UserData(self):
"""
This case was developed to test the user-data service of an instance for consistency.
This case does a comparison of the user data passed in by the user-data argument to
the data supplied by the user-data service within the instance. Supported
user data formats can be found here: https://cloudinit.readthedocs.org/en/latest/topics/format.html
If this test fails, the test case will error out; logging the results.
"""
if not self.reservation:
reservation = self.tester.run_instance(**self.run_instance_params)
else:
reservation = self.reservation
for instance in reservation.instances:
"""
Test to see if user data value is a file; if its a file, convert to string then compare,
if not, do a string compare
"""
if self.user_data_file:
with open(self.user_data_file) as user_data_file:
user_data = user_data_file.read()
instance_user_data = StringIO.StringIO(instance.get_userdata())
self.assertTrue(difflib.SequenceMatcher(None, instance_user_data.getvalue(), user_data), 'Incorrect User Data File')
elif self.user_data:
self.assertEqual(instance.get_userdata()[0], self.user_data, 'Incorrect User Data String')


if __name__ == "__main__":
testcase= EutesterTestCase(name='userdatatest')
testcase.setup_parser(description="Test the Eucalyptus EC2 instance store userdata functionality.")
testcase.get_args()
instancetestsuite= testcase.do_with_args(InstanceBasics)

### Either use the list of tests passed from config/command line to determine what subset of tests to run
list = testcase.args.tests or [ "UserData"]
### Convert test suite methods to EutesterUnitTest objects
unit_list = []
for test in list:
test = getattr(instancetestsuite,test)
unit_list.append(testcase.create_testunit_from_method(test))
testcase.clean_method = instancetestsuite.clean_method
result = testcase.run_test_case_list(unit_list)
exit(result)