diff --git a/roles/migration_getfacts/defaults/main.yml b/roles/migration_getfacts/defaults/main.yml
index 790ff69..cb79796 100644
--- a/roles/migration_getfacts/defaults/main.yml
+++ b/roles/migration_getfacts/defaults/main.yml
@@ -8,6 +8,9 @@ apis:
rolebinding:
default: "rbac.authorization.k8s.io/v1beta1"
"3.7": "v1"
+ deployment:
+ default: "apps/v1"
+ "3.7": "extensions/v1beta1"
daemonset:
default: "apps/v1"
"3.7": "extensions/v1beta1"
diff --git a/roles/migration_getfacts/tasks/main.yml b/roles/migration_getfacts/tasks/main.yml
index 0f63609..b4e466e 100644
--- a/roles/migration_getfacts/tasks/main.yml
+++ b/roles/migration_getfacts/tasks/main.yml
@@ -41,6 +41,7 @@
cronjob_api: "{{ apis.cronjob.get(oc_version, apis.cronjob.get('default')) }}"
role_api: "{{ apis.role.get(oc_version, apis.role.get('default')) }}"
rolebinding_api: "{{ apis.rolebinding.get(oc_version, apis.rolebinding.get('default')) }}"
+ deployment_api: "{{ apis.deployment.get(oc_version, apis.deployment.get('default')) }}"
daemonset_api: "{{ apis.daemonset.get(oc_version, apis.daemonset.get('default')) }}"
replicaset_api: "{{ apis.replicaset.get(oc_version, apis.replicaset.get('default')) }}"
statefulset_api: "{{ apis.statefulset.get(oc_version, apis.statefulset.get('default')) }}"
diff --git a/roles/ocp-29871-resquota/defaults/main.yml b/roles/ocp-29871-resquota/defaults/main.yml
new file mode 100644
index 0000000..01b6cd7
--- /dev/null
+++ b/roles/ocp-29871-resquota/defaults/main.yml
@@ -0,0 +1,17 @@
+namespace: "ocp-29871-resquota"
+
+app_name: nginxapp
+storage_size: 1Mi
+html_accessmode: ReadWriteOnce
+logs_accessmode: ReadWriteOnce
+
+migration_sample_name: "ocp-29871-resquota"
+migration_plan_name: "{{ migration_sample_name }}-migplan-{{ ansible_date_time.epoch }}"
+migration_name: "{{ migration_sample_name }}-mig-{{ ansible_date_time.epoch }}"
+
+with_deploy: true
+with_migrate: true
+with_cleanup: true
+with_validate: true
+pv: false
+quiesce: false
diff --git a/roles/ocp-29871-resquota/tasks/deploy.yml b/roles/ocp-29871-resquota/tasks/deploy.yml
new file mode 100644
index 0000000..bc16e69
--- /dev/null
+++ b/roles/ocp-29871-resquota/tasks/deploy.yml
@@ -0,0 +1,51 @@
+- name: Check namespace
+ k8s_facts:
+ kind: Namespace
+ name: "{{ namespace }}"
+ register: ns
+
+- name: Create namespace
+ shell: "{{ oc_binary }} new-project {{ namespace }} --skip-config-write=true"
+ when: ns.resources | length == 0
+
+
+- name: Get LimitRange
+ k8s_info:
+ kind: LimitRange
+ namespace: "{{ namespace }}"
+ register: limitrange
+ until: limitrange.resources | length > 0
+ ignore_errors: true
+
+- name: Remove LimitRanges so that default limit ranges dont interfere with the test
+ k8s:
+ kind: LimitRange
+ state : absent
+ namespace: "{{ namespace }}"
+ name: "{{ item.metadata.name }}"
+ loop: "{{ limitrange.resources | default([]) }}"
+
+- name: Create resource quota
+ k8s:
+ state : present
+ definition: "{{ lookup('template', 'resourcequota.yml.j2' )}}"
+
+- name: Create nginx deployment
+ k8s:
+ state : present
+ definition: "{{ lookup('template', 'nginx-deployment.yml.j2' )}}"
+
+- name: Wait unti nginx is up
+ k8s_facts:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ label_selectors: "app={{ app_name }}"
+ field_selectors:
+ - status.phase=Running
+ register: pod
+ until: "true in (pod | json_query('resources[].status.containerStatuses[].ready'))"
+ retries: 30
+
+- name: Upload an index html file
+ shell: "{{ oc_binary }} -n {{ namespace }} rsh $( {{oc_binary}} get pods -n {{ namespace }} -o jsonpath='{.items[0].metadata.name}') sh -c 'echo \"
HELLO WORLD
\" > /usr/share/nginx/html/index.html'"
+
diff --git a/roles/ocp-29871-resquota/tasks/main.yml b/roles/ocp-29871-resquota/tasks/main.yml
new file mode 100644
index 0000000..08175e4
--- /dev/null
+++ b/roles/ocp-29871-resquota/tasks/main.yml
@@ -0,0 +1,24 @@
+- name: Get oc facts
+ include_role:
+ name: migration_getfacts
+
+- name: Cleanup resources
+ include_role:
+ name: migration_cleanup
+ when: with_cleanup|bool
+
+- name: Create resources
+ import_tasks: deploy.yml
+ when: with_deploy|bool
+
+- name: Start migration
+ import_tasks: migrate.yml
+ when: with_migrate|bool
+
+- name: Track migration
+ import_tasks: track.yml
+ when: with_migrate|bool
+
+- name: Validate migration
+ import_tasks: validate.yml
+ when: with_validate|bool
diff --git a/roles/ocp-29871-resquota/tasks/migrate.yml b/roles/ocp-29871-resquota/tasks/migrate.yml
new file mode 100644
index 0000000..ac543a0
--- /dev/null
+++ b/roles/ocp-29871-resquota/tasks/migrate.yml
@@ -0,0 +1,3 @@
+- name: Execute migration
+ include_role:
+ name: migration_run
diff --git a/roles/ocp-29871-resquota/tasks/track.yml b/roles/ocp-29871-resquota/tasks/track.yml
new file mode 100644
index 0000000..8a3cf7c
--- /dev/null
+++ b/roles/ocp-29871-resquota/tasks/track.yml
@@ -0,0 +1,3 @@
+- name: Track migration
+ include_role:
+ name: migration_track
diff --git a/roles/ocp-29871-resquota/tasks/validate.yml b/roles/ocp-29871-resquota/tasks/validate.yml
new file mode 100644
index 0000000..91174b2
--- /dev/null
+++ b/roles/ocp-29871-resquota/tasks/validate.yml
@@ -0,0 +1,150 @@
+- name: Get Quota
+ k8s_info:
+ kind: ResourceQuota
+ namespace: "{{ namespace }}"
+ register: quota
+ retries: 20
+ until: quota.resources | length > 0
+
+- name: Get Pods
+ k8s_info:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ label_selectors: "app={{ app_name }}"
+ register: pod
+ retries: 20
+ until: pod.resources | length > 0
+
+#- fail:
+# msg: "There should be only one pod present, before and after the migration."
+# when: pod.resources == 1
+
+- name: Scale deployment and check that no more pods are allowed
+ k8s:
+ api_version: "{{ deployment_api }}"
+ kind: Deployment
+ name: "{{ app_name }}-deployment"
+ namespace: "{{ namespace }}"
+ definition:
+ spec:
+ replicas: 2
+ register: scale
+
+- name: Get ReplicaSet
+ k8s_info:
+ api_version: "{{ deployment_api }}"
+ kind: ReplicaSet
+ namespace: "{{ namespace }}"
+ label_selectors: "app={{ app_name }}"
+ register: replica
+ until: replica.resources | length > 0
+
+#- fail:
+# msg: "ReplicaSet should not have created new pods, because resourcequota does not allow it"
+# when: (replica.resources | first ).status.get('conditions', {}) | list | selectattr( 'type', 'equalto', 'ReplicaFailure') | list | length == 0
+
+- name: Scale down again
+ k8s:
+ api_version: "{{ deployment_api }}"
+ kind: Deployment
+ name: "{{ app_name }}-deployment"
+ namespace: "{{ namespace }}"
+ definition:
+ spec:
+ replicas: 1
+
+- name: Get Pods
+ k8s_info:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ label_selectors: "app={{ app_name }}"
+ register: pod
+ retries: 20
+ until: pod.resources | length == 1
+
+- name: Check that no LoadBalancer can be created
+ k8s:
+ state : present
+ definition: "{{ lookup('template', 'service.yml.j2' ) }}"
+ vars:
+ service_type: 'LoadBalancer'
+ service_app: 'balancersvc'
+ register: lbalancer
+ ignore_errors: true
+
+- fail:
+ msg: "FAILURE. Resource quota should not allow to create more LoadBalancers"
+ when: lbalancer.failed != true or lbalancer.error != 403
+
+- name: Check that no NodePort can be created
+ k8s:
+ state : present
+ definition: "{{ lookup('template', 'service.yml.j2' )}}"
+ vars:
+ service_type: 'NodePort'
+ service_app: 'podeportrsvc'
+ register: nodeport
+ ignore_errors: true
+
+- fail:
+ msg: "FAILURE. Resource quota should not allow to create more NodePort services"
+ when: nodeport.failed != true or nodeport.error != 403
+
+- name: Check that a ClusterIP service can be created. No resourcequota limit reached.
+ k8s:
+ state : present
+ definition: "{{ lookup('template', 'service.yml.j2' )}}"
+ vars:
+ service_type: 'ClusterIP'
+ service_app: 'clustersvc'
+ register: clusterip
+
+- name: Remove the created ClusterIP service
+ k8s:
+ state : absent
+ definition: "{{ lookup('template', 'service.yml.j2' )}}"
+ vars:
+ service_type: 'ClusterIP'
+ service_app: 'clustersvc'
+ register: clusterip
+
+- name: Check that no more PVCs can be created
+ k8s:
+ state : present
+ definition: "{{ lookup('template', 'persistent-volume-claim.yml.j2' )}}"
+ register: pvc
+ ignore_errors: true
+
+- fail:
+ msg: "FAILURE. Resource quota should not allow to create pvcs"
+ when: pvc.failed != true or pvc.error != 403
+
+- name: Get route
+ k8s_facts:
+ kind: Route
+ namespace: "{{ namespace }}"
+ label_selectors: "app={{ app_name }}"
+ register: nginx_route
+ until: nginx_route.resources | length > 0
+ retries: 30
+
+- name: Wait for pods running
+ k8s_info:
+ kind: Pod
+ namespace: "{{ namespace }}"
+ label_selectors: "app={{ app_name }}"
+ field_selectors:
+ - status.phase=Running
+ register: pod
+ retries: 20
+ until: pod.resources | length == 1
+
+- name: Acces the html file
+ uri:
+ url: http://{{ nginx_route.resources[0].spec.host }}
+ method: GET
+ status_code: 200
+ retries: 10
+ register: req
+ until: req.status == 200
+
diff --git a/roles/ocp-29871-resquota/templates/nginx-deployment.yml.j2 b/roles/ocp-29871-resquota/templates/nginx-deployment.yml.j2
new file mode 100644
index 0000000..a4a5b1d
--- /dev/null
+++ b/roles/ocp-29871-resquota/templates/nginx-deployment.yml.j2
@@ -0,0 +1,104 @@
+apiVersion: v1
+items:
+- apiVersion: v1
+ kind: PersistentVolumeClaim
+ metadata:
+ labels:
+ app: {{ app_name }}
+ name: {{ app_name }}-logs
+ namespace: {{ namespace }}
+ spec:
+ accessModes:
+ - {{ logs_accessmode }}
+ resources:
+ requests:
+ storage: {{ storage_size }}
+{% if storage_class is defined and storage_class != "default" %}
+ storageClassName: {{ storage_class | quote }}
+{% endif %}
+- apiVersion: v1
+ kind: PersistentVolumeClaim
+ metadata:
+ labels:
+ app: {{ app_name }}
+ name: {{ app_name }}-html
+ namespace: {{ namespace }}
+ spec:
+ accessModes:
+ - {{ html_accessmode }}
+ resources:
+ requests:
+ storage: 50Mi
+{% if storage_class is defined and storage_class != "default" %}
+ storageClassName: {{ storage_class | quote }}
+{% endif %}
+- apiVersion: {{ deployment_api }}
+ kind: Deployment
+ metadata:
+ labels:
+ app: {{ app_name }}
+ name: {{ app_name }}-deployment
+ namespace: {{ namespace }}
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: {{ app_name }}
+ template:
+ metadata:
+ labels:
+ app: {{ app_name }}
+ spec:
+ containers:
+ - image: docker.io/twalter/openshift-nginx
+ resources:
+ limits:
+ cpu: "1"
+ memory: 128Mi
+ name: {{ app_name }}
+ ports:
+ - containerPort: 8081
+ volumeMounts:
+ - mountPath: /var/log/nginx
+ name: {{ app_name }}-logs
+ readOnly: false
+ - mountPath: /usr/share/nginx/html
+ name: {{ app_name }}-html
+ readOnly: false
+ volumes:
+ - name: {{ app_name }}-logs
+ persistentVolumeClaim:
+ claimName: {{ app_name }}-logs
+ - name: {{ app_name }}-html
+ persistentVolumeClaim:
+ claimName: {{ app_name }}-html
+- apiVersion: v1
+ kind: Service
+ metadata:
+ labels:
+ app: {{ app_name }}
+ name: my-{{ app_name }}
+ namespace: {{ namespace }}
+ spec:
+ ports:
+ - port: 8081
+ targetPort: 8081
+ selector:
+ app: {{ app_name }}
+ type: ClusterIP
+- apiVersion: route.openshift.io/v1
+ kind: Route
+ metadata:
+ labels:
+ app: {{ app_name }}
+ service: my-{{ app_name }}
+ name: my-{{ app_name }}
+ namespace: {{ namespace }}
+ spec:
+ port:
+ targetPort: 8081
+ to:
+ kind: Service
+ name: my-{{ app_name }}
+kind: List
+metadata: {}
diff --git a/roles/ocp-29871-resquota/templates/persistent-volume-claim.yml.j2 b/roles/ocp-29871-resquota/templates/persistent-volume-claim.yml.j2
new file mode 100644
index 0000000..2e1860a
--- /dev/null
+++ b/roles/ocp-29871-resquota/templates/persistent-volume-claim.yml.j2
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: {{ namespace }}-test
+ namespace: {{ namespace }}
+spec:
+ storageClassName: manual
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 1Mi
diff --git a/roles/ocp-29871-resquota/templates/resourcequota.yml.j2 b/roles/ocp-29871-resquota/templates/resourcequota.yml.j2
new file mode 100644
index 0000000..9d3dd3b
--- /dev/null
+++ b/roles/ocp-29871-resquota/templates/resourcequota.yml.j2
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: ResourceQuota
+metadata:
+ name: object-quota
+ namespace: {{ namespace }}
+spec:
+ hard:
+ persistentvolumeclaims: "2"
+ services.loadbalancers: "0"
+ services.nodeports: "0"
+ pods: "1"
+ replicationcontrollers: "1"
+ secrets: "6"
+ configmaps: "4"
+ services: "10"
+ limits.cpu: "20"
+ limits.memory: 20Gi
+ requests.cpu: "10"
+ requests.memory: 10Gi
diff --git a/roles/ocp-29871-resquota/templates/service.yml.j2 b/roles/ocp-29871-resquota/templates/service.yml.j2
new file mode 100644
index 0000000..768005f
--- /dev/null
+++ b/roles/ocp-29871-resquota/templates/service.yml.j2
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ app: {{ service_app }}
+ name: my-{{ service_app }}
+ namespace: {{ namespace }}
+spec:
+ ports:
+ - port: 9999
+ targetPort: 8888
+ selector:
+ app: {{ service_app }}
+ type: {{ service_type }}