Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testing container in OpenShift #164

Merged
merged 8 commits into from
Nov 16, 2017
Merged

testing container in OpenShift #164

merged 8 commits into from
Nov 16, 2017

Conversation

phracek
Copy link
Member

@phracek phracek commented Nov 8, 2017

This Pull Request brings possibility to test containers in OpenShift environment.

MTF provides two scripts for preparing environment like MODULE=openshift mtf-env-set and MODULE=openshift mtf-env-clean

Usage is like:
MODULE=openshift OPENSHIFT_IP="Openshift_IP_Address" OPENSHIFT_USERNAME=developer OPENSHIFT_PASSWD=developer mtf -l *.py

The Pull Request fixes issue #89

# INTERFACE CLASSES FOR SPECIFIC MODULE TESTS
class OpenShiftAvocadoTest(AvocadoTest):
"""
Class for writing tests specific just for DOCKER
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/DOCKER/OpenShift/

self.cancel("OpenShift specific test")
super(OpenShiftAvocadoTest, self).setUp()

def checkLabel(self, key, value):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this method up to date?


def stop(self):
"""
Stop the docker container
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring not up to date

"""
start the OpenShift application

:param args: Do not use it directly (It is defined in config.yaml)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method doesn't accept any arguments

self.app_ip = service["items"][0]["spec"]["clusterIP"]
common.trans_dict['GUESTIPADDR'] = self.app_ip
common.print_info(common.trans_dict)
return True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring says that this method should return an ip address

common.trans_dict['GUESTIPADDR'] = self.app_ip
common.print_info(common.trans_dict)
return True
except KeyError as e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can also throw IndexError and TypeError (None[0])

service = self._convert_string_to_json(oc_get_service.stdout)
try:
common.print_info(service["items"])
common.print_info(service["items"][0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if there are other services running?

"""
return json.loads(
self.runHost(
"docker inspect %s" %
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openshift is not tied to docker


def _openshift_login(self, oc_ip="127.0.0.1", oc_user='developer', oc_passwd='developer', env=False):
if env:
if 'OPENSHIFT_IP' in os.environ:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation would be lovely

Copy link
Member

@TomasTomecek TomasTomecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs still need more love

**OpenShift**

- Install OpenShift if not installed and if environment variable ``OPENSHIFT_LOCAL`` is specified.
- if ``OPENSHIFT_LOCAL`` variable is specified, the it starts an OpenShift by command ``oc cluster up`` or stops it by command ``oc cluster down``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then*

- **OPENSHIFT_LOCAL** enables installing ``origin`` and ``origin-clients`` on local machine
- **OPENSHIFT_IP** uses this IP address for connecting to an OpenShift environment.
- **OPENSHIFT_USER** uses this ``USER`` name for login to an OpenShift environment.
- **OPENSHIFT_PWD** uses this ``PWD`` name for login to an OpenShift environment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PWD in our world means "print working directory"

@@ -25,6 +25,10 @@ Environment variables allow to overwrite some values of a module configuration f
- **MTF_REMOTE_REPOS=yes** disables downloading of Koji packages and creating a local repo, and speeds up test execution.
- **MTF_DISABLE_MODULE=yes** disables module handling to use nonmodular test mode (see `multihost tests`_ as an example).
- **DOCKERFILE="<path_to_dockerfile"** overwrites the location of a Dockerfile.
- **OPENSHIFT_LOCAL** enables installing ``origin`` and ``origin-clients`` on local machine
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to follow the previous definitions: e.g. what is the sample value for this variable?

from moduleframework.mtfexceptions import ContainerExc, ConfigExc


class OpenShiftHelper(CommonFunctions):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So how do I actually use this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used from commandline sudo MODULE=openshift mtf which executes this class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sweet! include it in docs please

Copy link
Collaborator

@jscotka jscotka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for this PR, few comments inside code

@@ -513,6 +513,8 @@ def getIPaddr(self):

:return: str
"""
if 'openshift' == os.environ.get('MODULE'):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this? in case you set self.ipaddr in Openshift helper it return proper value.


def __prepare_selinux(self):
# disable selinux by default if not turned off
if not os.environ.get('MTF_SKIP_DISABLING_SELINUX'):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this? openshift is also crappy with selinux enabled (nspawn is crappy with selinux enabled)?


:return: None
"""
if os.environ.get('OPENSHIFT_LOCAL'):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move os.environ.get('OPENSHIFT_LOCAL') to own function in common module and then use it instead of this env variable check.

from moduleframework.mtfexceptions import ContainerExc, ConfigExc


class OpenShiftHelper(CommonFunctions):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doen't make bigger sense to have ContainerHelper as parent instead of CommonFunctions in case docker is close to openshitft. in case it does not make sense, then this is okay.

and also remember CONU project. If we expect similar code in CONU, woule be better to be closer to docker class, and then you can remove some functions what are already defined in ContainerHelper

self.app_ip = None
common.print_debug(self.icontainer, self.app_name)

def get_container_url(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as an example, this method is same as in ContainerHelper

"""
if env:
if 'OPENSHIFT_IP' in os.environ:
oc_ip = os.environ.get('OPENSHIFT_IP')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move all os.environ.get to separated function, then you don't need to do this code and do it simplier and cleaner like: oc_ip = common.get_os_ip() or oc_ip

oc_user,
oc_passwd),
verbose=common.is_not_silent())
common.print_debug(oc_output.stdout)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why you use this debugging? isn't better to use verbose=variable directly. it exactly does what you want
verbose=is_debug() print via avocado run command output to logger and it is there. in case DEBUG is not set, it is not printed out. (when you don't use it with avocado runner, log is not shown, but it can be enabled.)

try:
for svc in service["items"]:
if "clusterIP" in svc.get("spec"):
common.trans_dict['GUESTIPADDR'] = svc.get("spec").get("clusterIP")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this is proper place where to modify also self.ipaddr variable and then you can remove some special handling of ipadd in common.CommonFunciton for OS

and maybe question, why there is for cycle. means that there can be more ipadresses and and the last one is the best one?
or you meant code like:

for svc un service.get("items", []):
    ipaddr = svc.get("spec",{}).get("clusterIP")
    if ipaddr:
          common.trans_dict['GUESTIPADDR'] = ipaddr
          self.ipaddr = ipaddr
          return True
return False

?

:param kwargs: dict
:return: avocado.process.run
"""
return self.runHost("%s" % common.sanitize_cmd(command), **kwargs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that this does what you expect?
this run command on host.
run method of helper should do command on GUEST in this case on openshift instance in case of docker there is for example docker exec INSTANCE_ID command so in case of openshift I expect something similar like oc exec ...

it is connected with more missing special methods for openshift, like CopyFrom and CopyTo now are inherited from CommonFunctions, what leads to host to host implementation, I expect there also some special oc command, or do it via some ssh, or whatever how it is connected together

Probably my mistake were that I've defined these functions leads to localhost operations, instead of raising not defined exceptions, and implement these methods where it make sense like in RpmHelper, ContainerHelper etc...

Copy link
Member

@TomasTomecek TomasTomecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functionality seems like exactly what we need -- basic testing using oc new-app. The one comment I have here is that users should probably be able to construct the CLI themselves, the same as you do for docker run.

For implementation: it's really fragile -- sometimes you get json from the commands (good) and process it, sometimes you don't. You also ignore the fact that they may be other applications being deployed in the cluster -- or even other MTF tests being run in parallel.

False, application does not exist
"""
oc_status = self.runHost("oc status", ignore_status=True)
if 'dc/%s' % self.app_name in oc_status.stdout:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not doing oc get dc $APP_NAME?

return True
oc_services = self.runHost("oc get services -o json")
json_svc = self._convert_string_to_json(oc_services.stdout)
if not json_svc["items"]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if I have ANY service deployed in my openshift cluster, this will return true

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as @TomasTomecek mentioned, probably you need to add there some "service" specific calls. I don't know it but probably there will be something similar to docker container inspect CONTAINER_ID what will show state just for one service instead of list all services and try to get some data

# dovecot 172.30.1.1:5000/myproject/dovecot latest 15 minutes ago
# memcached 172.30.1.1:5000/myproject/memcached latest 13 minutes ago

app_found = [x for x in oc_get.split('\n') if x.startswith(self.app_name)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is really fragile, why not using json as an output from oc get?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to use it.

It creates an application in OpenShift environment
"""
# Switching to system user
oc_new_app = self.runHost("oc new-app %s --name=%s" % (self.container_name,
Copy link
Member

@TomasTomecek TomasTomecek Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should add a label here to mark the deployment being owned by mtf: oc new-app -l mtf_testing=true or something like that; and then operate on objects which have this label.

Edit: so we probably need two identifiers: one which mark the app as one deployed by MTF, and the other will be unique identifier for the test run.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phracek @TomasTomecek yep, similar to docker container ... commands and then you will just use this tag. otherwise I expect that this iddentifier will be stored in OpenShiftHelper class like self.jmeno for ContainerHelper

"""
pod_initiated = False
for x in range(0, 20):
pod_state = self.runHost("oc get pods", ignore_status=True)
Copy link
Member

@TomasTomecek TomasTomecek Nov 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use json and don't parse CLI output

:param oc_user: an username under which we can login to OpenShift environment
:param oc_passwd: a password for specific username
:param env:
:return:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment should be: env: is used for specification OpenShift IP, user and password, otherwise defaults are used

:param env:
:return:
"""
if env:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably don't understand this logic

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make sense to you?
env: is used for specification OpenShift IP, user and password, otherwise defaults are used

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I still don't understand the reason for that variablee, since the values are being picked up from environment

"""
super(OpenShiftHelper, self).tearDown()
if common.get_if_do_cleanup():
# TODO will be implemented later on. I have to find a usecase what to remove
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably remove everything which was put into openshift during this test


def status(self):
"""
get status of an OpenShift
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't seem to correspond with the actual code


:return: bool
"""
if self._app_exists():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can easily return self._app_exists()

Signed-off-by: Petr "Stone" Hracek <[email protected]>
Signed-off-by: Petr "Stone" Hracek <[email protected]>
Signed-off-by: Petr "Stone" Hracek <[email protected]>
Copy link
Member

@TomasTomecek TomasTomecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks a lot better! Can't wait to give this a shot!

I think that some docstrings are still misleading and would love to have them improved.


def status(self):
"""
get status of an application in OpenShift environment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that this docstring doesn't correspond with the code: what does the "status" mean exactly? For me, status is usually enum, something like "running", "failed", "finished" and this method returns bool. It makes me confused.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be solved in whole MTF. status function always return bool value like e.g. here: https://github.com/fedora-modularity/meta-test-family/blob/devel/moduleframework/common.py#L552
https://github.com/fedora-modularity/meta-test-family/blob/devel/moduleframework/helpers/container_helper.py#L171

I have filed an issue for it here: #168

I will keep it now as it is.


def stop(self):
"""
Stops the OpenShift
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another misleading docstring; I think this method actually removes the application from the cluster

"""
starts the OpenShift application

:param args: Do not use it directly (It is defined in config.yaml)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function doesn't accept any args

common.print_info(e.message)
return False

def getIPaddr(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pythonic way of doing setters and getters is like this: https://docs.python.org/3/library/functions.html#property

(not blocking this review, just a friendly advice)

Copy link
Member Author

@phracek phracek Nov 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have filed an issue for it #167

service = self._convert_string_to_json(oc_get_service.stdout)
try:
for svc in service:
if svc.get('metadata').get('labels').get('app') == self.app_name:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1


def _get_ip_instance(self):
"""
It gets and IP address of an application from of OpenShift POD.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about: "This method verifies that we can obtain an IP address of the application deployed within OpenShift."

@phracek
Copy link
Member Author

phracek commented Nov 15, 2017

Hopefully, I have addressed all issues. The rest are filed as a new issues.

Copy link
Member

@TomasTomecek TomasTomecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, nicely done! let's give this a go

@phracek phracek merged commit 9b7210e into fedora-modularity:devel Nov 16, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants