4주차 과제 - 이성현(2)

/Users/sunghyun/.config/openstack/clouds.yaml 파일로 인증 정보 생성

# /Users/sunghyun/.config/openstack/clouds.yaml
clouds:
  devstack-admin:
    auth:
      auth_url: http://180.210.82.151/identity
      username: admin
      password: 2025_osc_openstack
      project_name: admin
      user_domain_name: default
      project_domain_name: default
    region_name: RegionOne
    identity_api_version: 3
    volume_api_version: 3
    
  devstack:
    auth:
      auth_url: http://180.210.82.151/identity
      username: demo
      password: 2025_osc_openstack
      project_name: demo
      user_domain_name: default
      project_domain_name: default
    region_name: RegionOne
    identity_api_version: 3
    volume_api_version: 3

  devstack-alt:
    auth:
      auth_url: http://180.210.82.151/identity
      username: alt_demo
      password: 2025_osc_openstack
      project_name: alt_demo
      user_domain_name: default
      project_domain_name: default
    region_name: RegionOne
    identity_api_version: 3
    volume_api_version: 3

  devstack-system-admin:
    auth:
      auth_url: http://180.210.82.151/identity
      username: admin
      password: 2025_osc_openstack
      project_name: admin
      user_domain_name: default
      project_domain_name: default
    region_name: RegionOne
    identity_api_version: 3
    volume_api_version: 3

테스트 코드

# openstack/tests/functional/identity/v3/test_service.py

import uuid
from openstack.tests.functional import base  # OpenStack SDK functional test 기본 클래스
from openstack import exceptions  # OpenStack 예외 클래스들


class TestService(base.BaseFunctionalTest):
    """Identity v3 Service resource functional tests.
    
    OpenStack Identity 서비스의 Service 리소스에 대한 기능 테스트
    실제 DevStack 환경에서 API 호출을 통해 검증
    """

    def setUp(self):
        """테스트 시작 전 초기 설정"""
        super().setUp()
        # operator_cloud: 관리자 권한 클라우드 연결 (devstack-admin)
        self.conn = self.operator_cloud
        
        # Admin 권한 확인 - 서비스 관리는 관리자만 가능
        try:
            list(self.conn.identity.services())
        except Exception as e:
            # 연결 실패 시 테스트 건너뛰기
            self.skipTest(f"Identity service unavailable: {e}")

    def test_service_create_delete(self):
        """서비스 생성 및 삭제 테스트"""
        # 고유한 서비스명/타입 생성 - 테스트 간 충돌 방지
        service_name = self.getUniqueString('func-service')
        service_type = self.getUniqueString('func-type')
        
        # CREATE: 새 서비스 생성
        service = self.conn.identity.create_service(
            name=service_name,
            type=service_type,
            description='Functional test service',
            enabled=True  # 활성화 상태로 생성
        )
        
        # 테스트 실패 시에도 리소스 정리 보장
        self.addCleanup(self._cleanup_service, service.id)
        
        # 생성 결과 검증
        self.assertEqual(service.name, service_name)
        self.assertEqual(service.type, service_type)
        self.assertTrue(service.is_enabled)  # enabled=True 확인
        self.assertIsNotNone(service.id)  # ID 할당 확인
        
        # DELETE: 수동 삭제 테스트
        self.conn.identity.delete_service(service.id)
        # 삭제 확인 - find 메서드로 존재 여부 체크
        deleted_service = self.conn.identity.find_service(
            service.id, ignore_missing=True  # 없어도 예외 발생 안함
        )
        self.assertIsNone(deleted_service)  # 삭제되어 None 반환 확인

    def test_service_show(self):
        """서비스 조회 기능 테스트"""
        service_name = self.getUniqueString('show-service')
        service_type = self.getUniqueString('show-type')
        
        # 테스트용 서비스 생성
        created_service = self.conn.identity.create_service(
            name=service_name,
            type=service_type
        )
        self.addCleanup(self._cleanup_service, created_service.id)
        
        # READ: ID로 서비스 조회
        retrieved_service = self.conn.identity.get_service(created_service.id)
        
        # 조회 결과와 생성한 서비스 일치 확인
        self.assertEqual(retrieved_service.id, created_service.id)
        self.assertEqual(retrieved_service.name, service_name)
        self.assertEqual(retrieved_service.type, service_type)

    def test_service_list(self):
        """서비스 목록 조회 테스트"""
        service_name = self.getUniqueString('list-service')
        service_type = self.getUniqueString('list-type')
        
        # 테스트용 서비스 생성
        created_service = self.conn.identity.create_service(
            name=service_name,
            type=service_type
        )
        self.addCleanup(self._cleanup_service, created_service.id)
        
        # 전체 서비스 목록 조회
        services = list(self.conn.identity.services())
        service_ids = [s.id for s in services]  # ID 목록 추출
        
        # 생성한 서비스가 목록에 포함되어 있는지 확인
        self.assertIn(created_service.id, service_ids)

    def test_service_set(self):
        """서비스 수정 기능 테스트"""
        service_name = self.getUniqueString('set-service')
        service_type = self.getUniqueString('set-type')
        
        # 수정할 서비스 생성
        service = self.conn.identity.create_service(
            name=service_name,
            type=service_type,
            enabled=True  # 초기값: 활성화
        )
        self.addCleanup(self._cleanup_service, service.id)
        
        # UPDATE: 설명과 상태 변경
        updated_description = 'Updated functional test service'
        updated_service = self.conn.identity.update_service(
            service.id,
            description=updated_description,
            enabled=False  # 비활성화로 변경
        )
        
        # 수정 결과 검증
        self.assertEqual(updated_service.description, updated_description)
        self.assertFalse(updated_service.is_enabled)  # 비활성화 확인

    def test_service_find(self):
        """이름으로 서비스 찾기 테스트"""
        service_name = self.getUniqueString('find-service')
        service_type = self.getUniqueString('find-type')
        
        # 찾을 서비스 생성
        service = self.conn.identity.create_service(
            name=service_name,
            type=service_type
        )
        self.addCleanup(self._cleanup_service, service.id)
        
        # 이름으로 서비스 검색
        found_service = self.conn.identity.find_service(service_name)
        
        # 검색 결과 검증
        self.assertIsNotNone(found_service)  # 찾기 성공
        self.assertEqual(found_service.id, service.id)  # 동일한 서비스 확인

    def test_service_crud_workflow(self):
        """전체 CRUD 작업 순서 테스트"""
        service_name = self.getUniqueString('crud-service')
        service_type = self.getUniqueString('crud-type')
        
        # CREATE: 서비스 생성
        service = self.conn.identity.create_service(
            name=service_name,
            type=service_type,
            description='CRUD workflow test'
        )
        service_id = service.id
        
        # 테스트 실패 시에도 삭제 보장 - 리소스 누수 방지
        self.addCleanup(self._cleanup_service, service_id)
        
        # READ: 생성된 서비스 조회
        retrieved = self.conn.identity.get_service(service_id)
        self.assertEqual(retrieved.name, service_name)
        
        # UPDATE: 서비스 정보 수정
        updated = self.conn.identity.update_service(
            service_id, 
            description='Updated CRUD test',
            enabled=False
        )
        self.assertEqual(updated.description, 'Updated CRUD test')
        self.assertFalse(updated.is_enabled)
        
        # DELETE: 서비스 삭제
        self.conn.identity.delete_service(service_id)
        # 삭제 확인
        deleted = self.conn.identity.find_service(
            service_id, ignore_missing=True
        )
        self.assertIsNone(deleted)

    def test_service_exception_handling(self):
        """예외 상황 처리 테스트"""
        fake_service_id = 'non-existent-service-id'  # 존재하지 않는 ID
        
        # 존재하지 않는 서비스 조회 시 예외 발생 확인
        try:
            self.conn.identity.get_service(fake_service_id)
            self.fail("Expected exception not raised")  # 예외 안 발생 시 실패
        except Exception:
            pass  # 예외 발생이 정상 동작
        
        # 존재하지 않는 서비스 수정 시 예외 발생 확인
        try:
            self.conn.identity.update_service(
                fake_service_id, description='This will fail'
            )
            self.fail("Expected exception not raised")
        except Exception:
            pass  # 예외 발생이 정상 동작
        
        # ignore_missing=True 옵션 테스트 - 예외 발생하면 안됨
        try:
            self.conn.identity.delete_service(fake_service_id, ignore_missing=True)
        except Exception as e:
            self.fail(f"delete_service with ignore_missing=True should not raise: {e}")

    def test_service_name_uniqueness(self):
        """서비스 이름 중복 허용 여부 테스트"""
        service_name = self.getUniqueString('unique-service')
        service_type1 = self.getUniqueString('unique-type1')
        service_type2 = self.getUniqueString('unique-type2')
        
        # 첫 번째 서비스 생성
        service1 = self.conn.identity.create_service(
            name=service_name,
            type=service_type1
        )
        self.addCleanup(self._cleanup_service, service1.id)
        
        # 같은 이름, 다른 타입으로 두 번째 서비스 생성
        # OpenStack에서는 이름 중복이 허용됨
        service2 = self.conn.identity.create_service(
            name=service_name,
            type=service_type2
        )
        self.addCleanup(self._cleanup_service, service2.id)
        
        # 두 서비스가 모두 생성되었는지 확인 (서로 다른 ID)
        self.assertNotEqual(service1.id, service2.id)

    def _cleanup_service(self, service_id):
        """테스트용 서비스 정리 헬퍼 메서드
        
        Args:
            service_id: 삭제할 서비스 ID
        """
        try:
            # ignore_missing=True: 이미 삭제된 경우 에러 무시
            self.conn.identity.delete_service(service_id, ignore_missing=True)
        except Exception:
            # 모든 예외 무시 - cleanup은 실패해도 테스트에 영향 없음
            pass

openstack service 클래스에 관한 테스트

tox -e functional -- openstack.tests.functional.identity.v3.test_service