Commit 0906e477 authored by Yann Garcia's avatar Yann Garcia
Browse files

Add MEC App service creation/deletion feature

parent baa6ffad
Loading
Loading
Loading
Loading
+260 −135
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ type Config struct {
}

// MEC 011 registration
// ETSI GS MEC 011 V3.2.1 (2024-04) 7.1.2.6 Type: AppInfo
// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 7.1.2.6 Type: AppInfo
type AppInfo struct {
	appName       string `json:"appName"`                 // Name of the application. It shall be consistent with the appName in the AppD, if an AppD is available.
	appProvider   string `json:"appProvider,omitempty"`   //Provider of the application. It shall be consistent with the appProvider in the AppD, if an AppD is available
@@ -72,6 +72,26 @@ type AppInfo struct {
	isInsByMec    bool   `json:"isInsByMec,omitempty"`    // Indicate whether the application instance is instantiated by the MEC Management. Default to FALSE if absent.
}

// MEC 011 ServiceInfo
// ETSI GS MEC 011 V3.2.1 (2024-04) Clause 8.1.2.2 Type: ServiceInfo
type ServiceInfo struct {
	serInstanceId string `json:"serInstanceId,omitempty"`
	serName       string `json:"serName"`
	serCategory   string `json:"serCategory,omitempty"`
	version       string `json:"version"` // Service version
	state         string `json:"state"`
	serializer    string `json:"serializer"`
	links         Links  `json:"_links"`
}

type LinkType struct {
	// URI referring to a resource
	href string `json:"href,omitempty"`
}
type Links struct {
	self *LinkType `json:"self"`
}

var (
	dir            string
	fileName       string
@@ -89,6 +109,7 @@ var (
	appsInfo       client.ApplicationInfo
	mecUrl         string = ""
	mecPlateform   string = ""
	appServiceInfo ServiceInfo
)

// Display menu and read selection
@@ -106,6 +127,8 @@ const (
	MEC011_CONFIRM_READY  = "y"
	MEC011_REGISTRATION   = "r"
	MEC011_DEREGISTRATION = "R"
	MEC011_CREATE_SVC     = "v"
	MEC011_DELETE_SVC     = "V"
	MEC030_UU_SETTINGS    = "u"
	QUIT                  = "q"
)
@@ -124,12 +147,14 @@ func menu(message string) []string {
			"\t%s <index>: Get scenario description\n"+
			"\t%s: Get MEC services list\n"+
			"\t%s: Get application instances list, %s: Create a new application instance, %s: Delete a new application instance\n"+
			"MEC 011:\n"+
			"MEC 011 App Support:\n"+
			"\t%s: Send ConfirmReady, %s: Send Registration, %s: Send Deregistration\n"+
			"MEC 011 Service Management:\n"+
			"\t%s: Create new service, %s: Delete service\n"+
			"MEC 030:\n"+
			"\t%s: Get V2X UU unicast setting\n"+
			"%s: Quit\n",
		LOGIN, LOGOUT, LIST_SC, ACTIVATE, DEACTIVATE, SC, LIST_SERVICES, LIST_APP, CREATE_APP, DELETE_APP, MEC011_CONFIRM_READY, MEC011_REGISTRATION, MEC011_DEREGISTRATION, MEC030_UU_SETTINGS, QUIT)
		LOGIN, LOGOUT, LIST_SC, ACTIVATE, DEACTIVATE, SC, LIST_SERVICES, LIST_APP, CREATE_APP, DELETE_APP, MEC011_CONFIRM_READY, MEC011_REGISTRATION, MEC011_DEREGISTRATION, MEC011_CREATE_SVC, MEC011_DELETE_SVC, MEC030_UU_SETTINGS, QUIT)
	if message != "" {
		fmt.Println("Last message: ", message)
	}
@@ -152,6 +177,7 @@ func login() (string, error) {
	// Initialize g;lobal variables
	scenarioId = -1
	appsInfo.Id = ""
	appServiceInfo.serInstanceId = ""

	sandbox, _, err := cl.AuthorizationApi.Login(context.TODO(), provider)
	if err != nil {
@@ -178,6 +204,12 @@ func logout() error {
		time.Sleep(2 * time.Second)
	}

	if appServiceInfo.serInstanceId != "" {
		mec011_delete_service()
		appServiceInfo.serInstanceId = ""
		time.Sleep(2 * time.Second)
	}

	// Terminate scenario if any
	if scenarioId != -1 {
		terminateScenario(scenarios[scenarioId].Id)
@@ -421,6 +453,73 @@ func mec011_send_deregistration() (body []byte, response *http.Response, err err
	return body, response, nil
}

func mec011_create_service() (body []byte, response *http.Response, err error) {
	fmt.Println(">>> mec011_create_service")

	// Sanity checks
	if sandboxName == "" {
		return nil, nil, errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		return nil, nil, errors.New("No App instcance available")
	} else if appServiceInfo.serInstanceId != "" {
		return nil, nil, errors.New("A MEC service is already created")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/applications/" + appsInfo.Id + "/services"
	fmt.Println(">>> mec011_create_service: url: " + url)
	// Build message body
	appServiceInfo = ServiceInfo{
		serInstanceId: uuid.New().String(),
		serName:       "demo6 MEC Service",
		serCategory:   "Game",
		version:       "1.0.0",
		state:         "demo",
		serializer:    "JSON",
		links: Links{
			self: &LinkType{
				href: "http://yanngarcia.ddns.net/location/v3/notif/1",
			},
		},
	}
	json_body, err := json.Marshal(appServiceInfo)
	if err != nil {
		return nil, nil, err
	}
	io_body := bytes.NewReader(json_body)
	// Send request and await response
	body, response, err = send_mec_service_request(http.MethodPost, url, io_body, nil, nil, nil)
	if err != nil {
		return nil, nil, err
	}

	return body, response, nil
}

func mec011_delete_service() (err error) {
	fmt.Println(">>> mec011_delete_service")

	// Sanity checks
	if sandboxName == "" {
		errors.New("No sandbox available")
	} else if appsInfo.Id == "" {
		errors.New("No App instcance available")
	} else if appServiceInfo.serInstanceId == "" {
		return errors.New("No MEC service created")
	}

	// Set URL
	url := mecUrl + "/" + sandboxName + "/" + mecPlateform + "/mec_service_mgmt/v1/applications/" + appsInfo.Id + "/services/" + appServiceInfo.serInstanceId
	fmt.Println(">>> mec011_delete_service: url: " + url)
	// Send request and await response
	_, _, err := send_mec_service_request(http.MethodDelete, url, nil, nil, nil, nil)
	if err != nil {
		return err
	}

	return nil
}

func mec030_get_v2x_uu_unicast_setting() (body []byte, response *http.Response, err error) {
	fmt.Println(">>> mec030_get_v2x_uu_unicast_setting")

@@ -577,14 +676,87 @@ func main() {
			if strings.Compare(choice[0], "q") == 0 {
				run = false
				break
			} else if strings.Compare(choice[0], LOGIN) == 0 {
			} else {
				message = process_choice(choice)
			}

		} // End of 'for' statement

	}()

	// Listen for SIGKILL
	go func() {
		sigchan := make(chan os.Signal, 10)
		signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
		<-sigchan
		fmt.Println("Waiting to shut down program !")
		run = false
	}()

	// Start REST API Server
	go func() {
		fmt.Println(">>> Start REST API Server")

		router := NewRouter()
		methods := handlers.AllowedMethods([]string{"OPTIONS", "DELETE", "GET", "HEAD", "POST", "PUT"})
		header := handlers.AllowedHeaders([]string{"content-type"})
		fmt.Println(http.ListenAndServe(":80", handlers.CORS(methods, header)(router)))
		run = false
		fmt.Println("<<< Stop REST API Server")
	}()

	// Listen for demo6 error exit program
	go func() {
		<-done
		fmt.Println("Waiting to shut down program !")
		run = false
	}()

	for {
		// Invoke graceful termination upon program kill
		if !run {
			fmt.Println("Invoking demo6 graceful termination")
			if sandboxName != "" {
				logout()
			}
			break
		}
		time.Sleep(time.Second)
	}

	return
}

func LoadConfig(path string, name string) (config Config, err error) {
	viper.SetConfigType("yaml")
	viper.AddConfigPath(path)
	viper.SetConfigName(name)
	viper.AutomaticEnv()

	err = viper.ReadInConfig()
	if err != nil {
		return config, err
	}

	err = viper.Unmarshal(&config)
	if err != nil {
		return config, err
	}
	return

}

func process_choice(choice []string) string {

	var message string
	if strings.Compare(choice[0], LOGIN) == 0 {
		sandboxName, _ = login()
		message = fmt.Sprintf("Sandbox Id: %s", sandboxName)
	} else if strings.Compare(choice[0], LOGOUT) == 0 {
		err := logout()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = "Sandbox terminated"
	} else if strings.Compare(choice[0], LIST_SC) == 0 {
@@ -594,7 +766,7 @@ func main() {
		idx, err := verify_idx_len(choice[1], len(scenarios))
		if err != nil {
			message = fmt.Sprintf("Invalid index: %s", err.Error())
					continue
			return
		}
		scenario, _ = getScenario(scenarios[idx].Id)
		message = fmt.Sprintf("Scenario %s:", fmt.Sprint(scenario))
@@ -603,19 +775,19 @@ func main() {
		scenarioId, err = verify_idx_len(choice[1], len(scenarios))
		if err != nil {
			message = fmt.Sprintf("Invalid index: %s", err.Error())
					continue
			return
		}
		err = activateScenario(scenarios[scenarioId].Id)
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("Scenario %s activated (wait some seconds before the next command)", scenarios[scenarioId].Id)
	} else if strings.Compare(choice[0], DEACTIVATE) == 0 {
		err := terminateScenario(scenarios[scenarioId].Id)
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("Scenario %s terminated (wait some seconds before the next command)", scenarios[scenarioId].Id)
		scenarioId = -1
@@ -624,20 +796,20 @@ func main() {
		services, err = getListOfMECServices()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("Services:  %s", fmt.Sprint(services))
	} else if strings.Compare(choice[0], LIST_APP) == 0 {
		appsInfos, err := getListOfMECAppInstIds()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("AppInstIds:  %s", fmt.Sprint(appsInfos))
	} else if strings.Compare(choice[0], CREATE_APP) == 0 {
		if appsInfo.Id != "" {
			message = fmt.Sprintf("App instance id already created: %s", appsInfo.Id)
					continue
			return
		}
		appsInfo = client.ApplicationInfo{
			Id:       uuid.New().String(),
@@ -649,14 +821,14 @@ func main() {
		err := createMECAppInstId(&appsInfo)
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("appsInfo:  %s created", fmt.Sprint(appsInfo.Id))
	} else if strings.Compare(choice[0], DELETE_APP) == 0 {
		err := deleteMECAppInstId()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("appsInfo:  %s deleted", fmt.Sprint(appsInfo.Id))
		appsInfo.Id = ""
@@ -665,7 +837,7 @@ func main() {
		body, _, err := mec011_send_confirm_ready()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("response body:  %s", string(body))
	} else if strings.Compare(choice[0], MEC011_REGISTRATION) == 0 {
@@ -673,7 +845,7 @@ func main() {
		body, _, err := mec011_send_registration()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("response body:  %s", string(body))
	} else if strings.Compare(choice[0], MEC011_DEREGISTRATION) == 0 {
@@ -681,83 +853,36 @@ func main() {
		body, _, err := mec011_send_deregistration()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("response body:  %s", string(body))
			} else if strings.Compare(choice[0], MEC030_UU_SETTINGS) == 0 {
	} else if strings.Compare(choice[0], MEC011_CREATE_SVC) == 0 {
		var err error
				body, _, err := mec030_get_v2x_uu_unicast_setting()
		body, _, err := mec011_create_service()
		if err != nil {
			message = err.Error()
					continue
			return
		}
		message = fmt.Sprintf("response body:  %s", string(body))
			} else {
				message = fmt.Sprintf("Invalid command: %s", choice)
			}

		} // End of 'for' statement

	}()

	// Listen for SIGKILL
	go func() {
		sigchan := make(chan os.Signal, 10)
		signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
		<-sigchan
		fmt.Println("Waiting to shut down program !")
		run = false
	}()

	// Start REST API Server
	go func() {
		fmt.Println(">>> Start REST API Server")

		router := NewRouter()
		methods := handlers.AllowedMethods([]string{"OPTIONS", "DELETE", "GET", "HEAD", "POST", "PUT"})
		header := handlers.AllowedHeaders([]string{"content-type"})
		fmt.Println(http.ListenAndServe(":80", handlers.CORS(methods, header)(router)))
		run = false
		fmt.Println("<<< Stop REST API Server")
	}()

	// Listen for demo6 error exit program
	go func() {
		<-done
		fmt.Println("Waiting to shut down program !")
		run = false
	}()

	for {
		// Invoke graceful termination upon program kill
		if !run {
			fmt.Println("Invoking demo6 graceful termination")
			if sandboxName != "" {
				logout()
			}
			break
		}
		time.Sleep(time.Second)
	}

	} else if strings.Compare(choice[0], MEC011_DELETE_SVC) == 0 {
		err := mec011_delete_service()
		if err != nil {
			message = err.Error()
			return
		}

func LoadConfig(path string, name string) (config Config, err error) {
	viper.SetConfigType("yaml")
	viper.AddConfigPath(path)
	viper.SetConfigName(name)
	viper.AutomaticEnv()

	err = viper.ReadInConfig()
		message = fmt.Sprintf("MEC Service deleted:  %s", appServiceInfo.serInstanceId)
		appServiceInfo.serInstanceId = ""
	} else if strings.Compare(choice[0], MEC030_UU_SETTINGS) == 0 {
		var err error
		body, _, err := mec030_get_v2x_uu_unicast_setting()
		if err != nil {
		return config, err
			message = err.Error()
			return
		}

	err = viper.Unmarshal(&config)
	if err != nil {
		return config, err
		message = fmt.Sprintf("response body:  %s", string(body))
	} else {
		message = fmt.Sprintf("Invalid command: %s", choice)
	}
	return

	return message
}