mirror of
				https://github.com/redhat-developer/odo.git
				synced 2025-10-19 03:06:19 +03:00 
			
		
		
		
	Fix Docker Supervisord Vol Initialization (#3129)
* apply stashed supervisord docker changes from PR Signed-off-by: Maysun J Faisal <maysun.j.faisal@ibm.com> * rebase 1 with master Signed-off-by: Maysun J Faisal <maysun.j.faisal@ibm.com>
This commit is contained in:
		| @@ -37,17 +37,21 @@ type Adapter struct { | ||||
| 	devfileBuildCmd           string | ||||
| 	devfileRunCmd             string | ||||
| 	supervisordVolumeName     string | ||||
| 	projectVolumeName         string | ||||
| } | ||||
|  | ||||
| // Push updates the component if a matching component exists or creates one if it doesn't exist | ||||
| func (a Adapter) Push(parameters common.PushParameters) (err error) { | ||||
| 	componentExists := utils.ComponentExists(a.Client, a.ComponentName) | ||||
| 	componentExists, err := utils.ComponentExists(a.Client, a.Devfile.Data, a.ComponentName) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "unable to determine if component %s exists", a.ComponentName) | ||||
| 	} | ||||
|  | ||||
| 	// Process the volumes defined in the devfile | ||||
| 	a.componentAliasToVolumes = common.GetVolumes(a.Devfile) | ||||
| 	a.uniqueStorage, a.volumeNameToDockerVolName, err = storage.ProcessVolumes(&a.Client, a.ComponentName, a.componentAliasToVolumes) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "Unable to process volumes for component %s", a.ComponentName) | ||||
| 		return errors.Wrapf(err, "unable to process volumes for component %s", a.ComponentName) | ||||
| 	} | ||||
|  | ||||
| 	a.devfileInitCmd = parameters.DevfileInitCmd | ||||
| @@ -64,19 +68,14 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) { | ||||
| 	} | ||||
| 	s.End(true) | ||||
|  | ||||
| 	// Get the supervisord volume | ||||
| 	supervisordLabels := utils.GetSupervisordVolumeLabels() | ||||
| 	supervisordVolumes, err := a.Client.GetVolumesByLabel(supervisordLabels) | ||||
| 	a.supervisordVolumeName, err = a.createAndInitSupervisordVolumeIfReqd(componentExists) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "unable to retrieve supervisord volume for component %s", a.ComponentName) | ||||
| 		return errors.Wrapf(err, "unable to create supervisord volume for component %s", a.ComponentName) | ||||
| 	} | ||||
| 	if len(supervisordVolumes) == 0 { | ||||
| 		a.supervisordVolumeName, err = utils.CreateAndInitSupervisordVolume(a.Client) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrapf(err, "unable to create supervisord volume for component %s", a.ComponentName) | ||||
| 		} | ||||
| 	} else { | ||||
| 		a.supervisordVolumeName = supervisordVolumes[0].Name | ||||
|  | ||||
| 	a.projectVolumeName, err = a.createProjectVolumeIfReqd() | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "unable to determine the project source volume for component %s", a.ComponentName) | ||||
| 	} | ||||
|  | ||||
| 	if componentExists { | ||||
| @@ -131,7 +130,8 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) { | ||||
|  | ||||
| // DoesComponentExist returns true if a component with the specified name exists, false otherwise | ||||
| func (a Adapter) DoesComponentExist(cmpName string) bool { | ||||
| 	return utils.ComponentExists(a.Client, cmpName) | ||||
| 	componentExists, _ := utils.ComponentExists(a.Client, a.Devfile.Data, cmpName) | ||||
| 	return componentExists | ||||
| } | ||||
|  | ||||
| // getFirstContainerWithSourceVolume returns the first container that set mountSources: true | ||||
| @@ -199,10 +199,10 @@ func (a Adapter) Delete(labels map[string]string) error { | ||||
|  | ||||
| 			if snVal := vol.Labels["storage-name"]; len(strings.TrimSpace(snVal)) > 0 { | ||||
| 				vols = append(vols, vol) | ||||
| 			} else { | ||||
| 				if typeVal := vol.Labels["type"]; typeVal == "projects" { | ||||
| 					vols = append(vols, vol) | ||||
| 				} | ||||
| 			} else if typeVal := vol.Labels["type"]; typeVal == utils.ProjectsVolume { | ||||
| 				vols = append(vols, vol) | ||||
| 			} else if typeVal := vol.Labels["type"]; typeVal == utils.SupervisordVolume { | ||||
| 				vols = append(vols, vol) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import ( | ||||
| 	"github.com/golang/mock/gomock" | ||||
| 	adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" | ||||
| 	devfileParser "github.com/openshift/odo/pkg/devfile/parser" | ||||
| 	"github.com/openshift/odo/pkg/devfile/parser/data/common" | ||||
| 	versionsCommon "github.com/openshift/odo/pkg/devfile/parser/data/common" | ||||
| 	"github.com/openshift/odo/pkg/lclient" | ||||
| 	"github.com/openshift/odo/pkg/testingutil" | ||||
| @@ -127,32 +128,39 @@ func TestDoesComponentExist(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name             string | ||||
| 		client           *lclient.Client | ||||
| 		componentType    versionsCommon.DevfileComponentType | ||||
| 		components       []common.DevfileComponent | ||||
| 		componentName    string | ||||
| 		getComponentName string | ||||
| 		want             bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:             "Case 1: Valid component name", | ||||
| 			client:           fakeClient, | ||||
| 			componentType:    versionsCommon.ContainerComponentType, | ||||
| 			name:   "Case 1: Valid component name", | ||||
| 			client: fakeClient, | ||||
| 			components: []common.DevfileComponent{ | ||||
| 				testingutil.GetFakeComponent("alias1"), | ||||
| 				testingutil.GetFakeComponent("alias2"), | ||||
| 			}, | ||||
| 			componentName:    "golang", | ||||
| 			getComponentName: "golang", | ||||
| 			want:             true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:             "Case 2: Non-existent component name", | ||||
| 			componentType:    versionsCommon.ContainerComponentType, | ||||
| 			client:           fakeClient, | ||||
| 			componentName:    "test-name", | ||||
| 			name:   "Case 2: Non-existent component name", | ||||
| 			client: fakeClient, | ||||
| 			components: []common.DevfileComponent{ | ||||
| 				testingutil.GetFakeComponent("alias1"), | ||||
| 			}, | ||||
| 			componentName:    "test", | ||||
| 			getComponentName: "fake-component", | ||||
| 			want:             false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:             "Case 3: Docker client error", | ||||
| 			componentType:    versionsCommon.ContainerComponentType, | ||||
| 			client:           fakeErrorClient, | ||||
| 			componentName:    "test-name", | ||||
| 			name:   "Case 3: Docker client error", | ||||
| 			client: fakeErrorClient, | ||||
| 			components: []common.DevfileComponent{ | ||||
| 				testingutil.GetFakeComponent("alias1"), | ||||
| 			}, | ||||
| 			componentName:    "test", | ||||
| 			getComponentName: "fake-component", | ||||
| 			want:             false, | ||||
| 		}, | ||||
| @@ -161,11 +169,7 @@ func TestDoesComponentExist(t *testing.T) { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			devObj := devfileParser.DevfileObj{ | ||||
| 				Data: testingutil.TestDevfileData{ | ||||
| 					Components: []versionsCommon.DevfileComponent{ | ||||
| 						{ | ||||
| 							Type: tt.componentType, | ||||
| 						}, | ||||
| 					}, | ||||
| 					Components: tt.components, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| @@ -571,6 +575,44 @@ func TestAdapterDeleteVolumes(t *testing.T) { | ||||
| 			}, | ||||
| 			expectToDelete: []string{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 7: Should delete both storage and supervisord mount", | ||||
| 			containers: []types.Container{ | ||||
| 				containerWithMount(componentName, | ||||
| 					[]types.MountPoint{ | ||||
| 						{ | ||||
| 							Name: "my-supervisord-mount", | ||||
| 							Type: mount.TypeVolume, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Name: "my-storage-mount", | ||||
| 							Type: mount.TypeVolume, | ||||
| 						}, | ||||
| 					}), | ||||
| 			}, | ||||
| 			volumes: []*types.Volume{ | ||||
| 				{ | ||||
| 					Name: "my-supervisord-mount", | ||||
| 					Labels: map[string]string{ | ||||
| 						"component": componentName, | ||||
| 						"type":      "supervisord", | ||||
| 						"image":     "supervisordimage", | ||||
| 						"version":   "supervisordversion", | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name: "my-storage-mount", | ||||
| 					Labels: map[string]string{ | ||||
| 						"component":    componentName, | ||||
| 						"storage-name": "anyval", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectToDelete: []string{ | ||||
| 				"my-supervisord-mount", | ||||
| 				"my-storage-mount", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tt := range tests { | ||||
|   | ||||
| @@ -25,7 +25,9 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	LocalhostIP             = "127.0.0.1" | ||||
| 	// LocalhostIP is the IP address for localhost | ||||
| 	LocalhostIP = "127.0.0.1" | ||||
|  | ||||
| 	projectSourceVolumeName = "odo-project-source" | ||||
| ) | ||||
|  | ||||
| @@ -34,39 +36,16 @@ func (a Adapter) createComponent() (err error) { | ||||
|  | ||||
| 	log.Infof("\nCreating Docker resources for component %s", a.ComponentName) | ||||
|  | ||||
| 	// Get or create the project source volume | ||||
| 	var projectVolumeName string | ||||
| 	projectVolumeLabels := utils.GetProjectVolumeLabels(componentName) | ||||
| 	projectVols, err := a.Client.GetVolumesByLabel(projectVolumeLabels) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "Unable to retrieve source volume for component "+componentName) | ||||
| 	} | ||||
| 	if len(projectVols) == 0 { | ||||
| 		// A source volume needs to be created | ||||
| 		projectVolumeName, err = storage.GenerateVolName(projectSourceVolumeName, a.ComponentName) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrapf(err, "Unable to generate project source volume name for component %s", componentName) | ||||
| 		} | ||||
| 		_, err := a.Client.CreateVolume(projectVolumeName, projectVolumeLabels) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrapf(err, "Unable to create project source volume for component %s", componentName) | ||||
| 		} | ||||
| 	} else if len(projectVols) == 1 { | ||||
| 		projectVolumeName = projectVols[0].Name | ||||
| 	} else if len(projectVols) > 1 { | ||||
| 		return errors.Wrapf(err, "Error, multiple source volumes found for component %s", componentName) | ||||
| 	} | ||||
|  | ||||
| 	supportedComponents := common.GetSupportedComponents(a.Devfile.Data) | ||||
| 	if len(supportedComponents) == 0 { | ||||
| 		return fmt.Errorf("No valid components found in the devfile") | ||||
| 		return fmt.Errorf("no valid components found in the devfile") | ||||
| 	} | ||||
|  | ||||
| 	// Get the storage adapter and create the volumes if it does not exist | ||||
| 	stoAdapter := storage.New(a.AdapterContext, a.Client) | ||||
| 	err = stoAdapter.Create(a.uniqueStorage) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "Unable to create Docker storage adapter for component %s", componentName) | ||||
| 		return errors.Wrapf(err, "unable to create Docker storage adapter for component %s", componentName) | ||||
| 	} | ||||
|  | ||||
| 	// Loop over each component and start a container for it | ||||
| @@ -81,7 +60,7 @@ func (a Adapter) createComponent() (err error) { | ||||
| 			} | ||||
| 			dockerVolumeMounts = append(dockerVolumeMounts, volMount) | ||||
| 		} | ||||
| 		err = a.pullAndStartContainer(dockerVolumeMounts, projectVolumeName, comp) | ||||
| 		err = a.pullAndStartContainer(dockerVolumeMounts, comp) | ||||
| 		if err != nil { | ||||
| 			return errors.Wrapf(err, "unable to pull and start container %s for component %s", comp.Container.Name, componentName) | ||||
| 		} | ||||
| @@ -96,26 +75,13 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { | ||||
| 	componentExists = true | ||||
| 	componentName := a.ComponentName | ||||
|  | ||||
| 	// Get the project source volume | ||||
| 	volumeLabels := utils.GetProjectVolumeLabels(componentName) | ||||
| 	projectVols, err := a.Client.GetVolumesByLabel(volumeLabels) | ||||
| 	if err != nil { | ||||
| 		return componentExists, errors.Wrapf(err, "Unable to retrieve source volume for component "+componentName) | ||||
| 	} | ||||
| 	if len(projectVols) == 0 { | ||||
| 		return componentExists, fmt.Errorf("Unable to find source volume for component %s", componentName) | ||||
| 	} else if len(projectVols) > 1 { | ||||
| 		return componentExists, errors.Wrapf(err, "Error, multiple source volumes found for component %s", componentName) | ||||
| 	} | ||||
| 	projectVolumeName := projectVols[0].Name | ||||
|  | ||||
| 	// Get the storage adapter and create the volumes if it does not exist | ||||
| 	stoAdapter := storage.New(a.AdapterContext, a.Client) | ||||
| 	err = stoAdapter.Create(a.uniqueStorage) | ||||
|  | ||||
| 	supportedComponents := common.GetSupportedComponents(a.Devfile.Data) | ||||
| 	if len(supportedComponents) == 0 { | ||||
| 		return componentExists, fmt.Errorf("No valid components found in the devfile") | ||||
| 		return componentExists, fmt.Errorf("no valid components found in the devfile") | ||||
| 	} | ||||
|  | ||||
| 	for _, comp := range supportedComponents { | ||||
| @@ -139,7 +105,7 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { | ||||
| 			log.Infof("\nCreating Docker resources for component %s", a.ComponentName) | ||||
|  | ||||
| 			// Container doesn't exist, so need to pull its image (to be safe) and start a new container | ||||
| 			err = a.pullAndStartContainer(dockerVolumeMounts, projectVolumeName, comp) | ||||
| 			err = a.pullAndStartContainer(dockerVolumeMounts, comp) | ||||
| 			if err != nil { | ||||
| 				return false, errors.Wrapf(err, "unable to pull and start container %s for component %s", comp.Container.Name, componentName) | ||||
| 			} | ||||
| @@ -174,13 +140,13 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { | ||||
| 				// Remove the container | ||||
| 				err := a.Client.RemoveContainer(containerID) | ||||
| 				if err != nil { | ||||
| 					return componentExists, errors.Wrapf(err, "Unable to remove container %s for component %s", containerID, comp.Container.Name) | ||||
| 					return componentExists, errors.Wrapf(err, "unable to remove container %s for component %s", containerID, comp.Container.Name) | ||||
| 				} | ||||
|  | ||||
| 				// Start the container | ||||
| 				err = a.startComponent(dockerVolumeMounts, projectVolumeName, comp) | ||||
| 				err = a.startComponent(dockerVolumeMounts, comp) | ||||
| 				if err != nil { | ||||
| 					return false, errors.Wrapf(err, "Unable to start container for devfile component %s", comp.Container.Name) | ||||
| 					return false, errors.Wrapf(err, "unable to start container for devfile component %s", comp.Container.Name) | ||||
| 				} | ||||
|  | ||||
| 				klog.V(3).Infof("Successfully created container %s for component %s", comp.Container.Image, componentName) | ||||
| @@ -192,14 +158,14 @@ func (a Adapter) updateComponent() (componentExists bool, err error) { | ||||
| 		} else { | ||||
| 			// Multiple containers were returned with the specified label (which should be unique) | ||||
| 			// Error out, as this isn't expected | ||||
| 			return true, fmt.Errorf("Found multiple running containers for devfile component %s and cannot push changes", comp.Container.Name) | ||||
| 			return true, fmt.Errorf("found multiple running containers for devfile component %s and cannot push changes", comp.Container.Name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (a Adapter) pullAndStartContainer(mounts []mount.Mount, projectVolumeName string, comp versionsCommon.DevfileComponent) error { | ||||
| func (a Adapter) pullAndStartContainer(mounts []mount.Mount, comp versionsCommon.DevfileComponent) error { | ||||
| 	// Container doesn't exist, so need to pull its image (to be safe) and start a new container | ||||
| 	s := log.Spinnerf("Pulling image %s", comp.Container.Image) | ||||
|  | ||||
| @@ -211,16 +177,16 @@ func (a Adapter) pullAndStartContainer(mounts []mount.Mount, projectVolumeName s | ||||
| 	s.End(true) | ||||
|  | ||||
| 	// Start the component container | ||||
| 	err = a.startComponent(mounts, projectVolumeName, comp) | ||||
| 	err = a.startComponent(mounts, comp) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "Unable to start container for devfile component %s", comp.Container.Name) | ||||
| 		return errors.Wrapf(err, "unable to start container for devfile component %s", comp.Container.Name) | ||||
| 	} | ||||
|  | ||||
| 	klog.V(3).Infof("Successfully created container %s for component %s", comp.Container.Image, a.ComponentName) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, comp versionsCommon.DevfileComponent) error { | ||||
| func (a Adapter) startComponent(mounts []mount.Mount, comp versionsCommon.DevfileComponent) error { | ||||
| 	hostConfig, namePortMapping, err := a.generateAndGetHostConfig(comp.Container.Endpoints) | ||||
| 	hostConfig.Mounts = mounts | ||||
| 	if err != nil { | ||||
| @@ -232,11 +198,11 @@ func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	utils.UpdateComponentWithSupervisord(&comp, runCommand, a.supervisordVolumeName, &hostConfig) | ||||
| 	updateComponentWithSupervisord(&comp, runCommand, a.supervisordVolumeName, &hostConfig) | ||||
|  | ||||
| 	// If the component set `mountSources` to true, add the source volume and env CHE_PROJECTS_ROOT to it | ||||
| 	if comp.Container.MountSources { | ||||
| 		utils.AddVolumeToContainer(projectVolumeName, lclient.OdoSourceVolumeMount, &hostConfig) | ||||
| 		utils.AddVolumeToContainer(a.projectVolumeName, lclient.OdoSourceVolumeMount, &hostConfig) | ||||
|  | ||||
| 		if !common.IsEnvPresent(comp.Container.Env, common.EnvCheProjectsRoot) { | ||||
| 			envName := common.EnvCheProjectsRoot | ||||
| @@ -257,7 +223,7 @@ func (a Adapter) startComponent(mounts []mount.Mount, projectVolumeName string, | ||||
| 	// Create the docker container | ||||
| 	s := log.Spinner("Starting container for " + comp.Container.Image) | ||||
| 	defer s.End(false) | ||||
| 	err = a.Client.StartContainer(&containerConfig, &hostConfig, nil) | ||||
| 	_, err = a.Client.StartContainer(&containerConfig, &hostConfig, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -335,7 +301,7 @@ func getPortMap(context string, endpoints []versionsCommon.Endpoint, show bool) | ||||
| 				log.Successf("URL %v:%v created", LocalhostIP, url.ExposedPort) | ||||
| 			} | ||||
| 		} else if url.ExposedPort > 0 && len(endpoints) > 0 && !common.IsPortPresent(endpoints, url.Port) { | ||||
| 			return nil, nil, fmt.Errorf("Error creating url: odo url config's port is not present in the devfile. Please re-create odo url with the new devfile port") | ||||
| 			return nil, nil, fmt.Errorf("error creating url: odo url config's port is not present in the devfile. Please re-create odo url with the new devfile port") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -385,7 +351,7 @@ func (a Adapter) execDevfile(commandsMap common.PushCommandsMap, componentExists | ||||
| 		// Check if the devfile run component containers have supervisord as the entrypoint. | ||||
| 		// Start the supervisord if the odo component does not exist | ||||
| 		if !componentExists { | ||||
| 			err = a.InitRunContainerSupervisord(command.Exec.Component, containers) | ||||
| 			err = a.initRunContainerSupervisord(command.Exec.Component, containers) | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| @@ -404,9 +370,9 @@ func (a Adapter) execDevfile(commandsMap common.PushCommandsMap, componentExists | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // InitRunContainerSupervisord initializes the supervisord in the container if | ||||
| // initRunContainerSupervisord initializes the supervisord in the container if | ||||
| // the container has entrypoint that is not supervisord | ||||
| func (a Adapter) InitRunContainerSupervisord(component string, containers []types.Container) (err error) { | ||||
| func (a Adapter) initRunContainerSupervisord(component string, containers []types.Container) (err error) { | ||||
| 	for _, container := range containers { | ||||
| 		if container.Labels["alias"] == component && !strings.Contains(container.Command, common.SupervisordBinaryPath) { | ||||
| 			command := []string{common.SupervisordBinaryPath, "-c", common.SupervisordConfFile, "-d"} | ||||
| @@ -419,3 +385,171 @@ func (a Adapter) InitRunContainerSupervisord(component string, containers []type | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // createProjectVolumeIfReqd creates a project volume if absent and returns the | ||||
| // name of the created project volume | ||||
| func (a Adapter) createProjectVolumeIfReqd() (string, error) { | ||||
| 	var projectVolumeName string | ||||
| 	componentName := a.ComponentName | ||||
|  | ||||
| 	// Get the project source volume | ||||
| 	projectVolumeLabels := utils.GetProjectVolumeLabels(componentName) | ||||
| 	projectVols, err := a.Client.GetVolumesByLabel(projectVolumeLabels) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrapf(err, "unable to retrieve source volume for component "+componentName) | ||||
| 	} | ||||
|  | ||||
| 	if len(projectVols) == 0 { | ||||
| 		// A source volume needs to be created | ||||
| 		projectVolumeName, err = storage.GenerateVolName(projectSourceVolumeName, componentName) | ||||
| 		if err != nil { | ||||
| 			return "", errors.Wrapf(err, "unable to generate project source volume name for component %s", componentName) | ||||
| 		} | ||||
| 		_, err := a.Client.CreateVolume(projectVolumeName, projectVolumeLabels) | ||||
| 		if err != nil { | ||||
| 			return "", errors.Wrapf(err, "unable to create project source volume for component %s", componentName) | ||||
| 		} | ||||
| 	} else if len(projectVols) == 1 { | ||||
| 		projectVolumeName = projectVols[0].Name | ||||
| 	} else if len(projectVols) > 1 { | ||||
| 		return "", errors.New(fmt.Sprintf("multiple source volumes found for component %s", componentName)) | ||||
| 	} | ||||
|  | ||||
| 	return projectVolumeName, nil | ||||
| } | ||||
|  | ||||
| // createAndInitSupervisordVolumeIfReqd creates the supervisord volume and initializes | ||||
| // it with supervisord bootstrap image - assembly files and supervisord binary | ||||
| // returns the name of the supervisord volume and an error if present | ||||
| func (a Adapter) createAndInitSupervisordVolumeIfReqd(componentExists bool) (string, error) { | ||||
| 	var supervisordVolumeName string | ||||
| 	componentName := a.ComponentName | ||||
|  | ||||
| 	supervisordLabels := utils.GetSupervisordVolumeLabels(componentName) | ||||
| 	supervisordVolumes, err := a.Client.GetVolumesByLabel(supervisordLabels) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrapf(err, "unable to retrieve supervisord volume for component") | ||||
| 	} | ||||
|  | ||||
| 	if len(supervisordVolumes) == 0 { | ||||
| 		supervisordVolumeName, err = storage.GenerateVolName(common.SupervisordVolumeName, componentName) | ||||
| 		if err != nil { | ||||
| 			return "", errors.Wrapf(err, "unable to generate volume name for supervisord") | ||||
| 		} | ||||
| 		_, err := a.Client.CreateVolume(supervisordVolumeName, supervisordLabels) | ||||
| 		if err != nil { | ||||
| 			return "", errors.Wrapf(err, "unable to create supervisord volume for component") | ||||
| 		} | ||||
| 	} else { | ||||
| 		supervisordVolumeName = supervisordVolumes[0].Name | ||||
| 	} | ||||
|  | ||||
| 	if !componentExists { | ||||
| 		log.Info("\nInitialization") | ||||
| 		s := log.Spinner("Initializing the component") | ||||
| 		defer s.End(false) | ||||
|  | ||||
| 		err = a.startBootstrapSupervisordInitContainer(supervisordVolumeName) | ||||
| 		if err != nil { | ||||
| 			return "", errors.Wrapf(err, "unable to start supervisord container for component") | ||||
| 		} | ||||
|  | ||||
| 		s.End(true) | ||||
| 	} | ||||
|  | ||||
| 	return supervisordVolumeName, nil | ||||
| } | ||||
|  | ||||
| // startBootstrapSupervisordInitContainer pulls the supervisord bootstrap image, mounts the supervisord | ||||
| // volume, starts the bootstrap container and initializes the supervisord volume via its entrypoint | ||||
| func (a Adapter) startBootstrapSupervisordInitContainer(supervisordVolumeName string) error { | ||||
| 	componentName := a.ComponentName | ||||
| 	supervisordLabels := utils.GetSupervisordVolumeLabels(componentName) | ||||
| 	image := common.GetBootstrapperImage() | ||||
| 	command := []string{"/usr/bin/cp"} | ||||
| 	args := []string{ | ||||
| 		"-r", | ||||
| 		common.OdoInitImageContents, | ||||
| 		common.SupervisordMountPath, | ||||
| 	} | ||||
|  | ||||
| 	var s *log.Status | ||||
| 	if log.IsDebug() { | ||||
| 		s = log.Spinnerf("Pulling image %s", image) | ||||
| 		defer s.End(false) | ||||
| 	} | ||||
|  | ||||
| 	err := a.Client.PullImage(image) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "unable to pull %s image", image) | ||||
| 	} | ||||
| 	if log.IsDebug() { | ||||
| 		s.End(true) | ||||
| 	} | ||||
|  | ||||
| 	containerConfig := a.Client.GenerateContainerConfig(image, command, args, nil, supervisordLabels, nil) | ||||
| 	hostConfig := container.HostConfig{} | ||||
|  | ||||
| 	utils.AddVolumeToContainer(supervisordVolumeName, common.SupervisordMountPath, &hostConfig) | ||||
|  | ||||
| 	// Create the docker container | ||||
| 	if log.IsDebug() { | ||||
| 		s = log.Spinnerf("Starting container for %s", image) | ||||
| 		defer s.End(false) | ||||
| 	} | ||||
| 	containerID, err := a.Client.StartContainer(&containerConfig, &hostConfig, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if log.IsDebug() { | ||||
| 		s.End(true) | ||||
| 	} | ||||
|  | ||||
| 	// Wait for the container to exit before removing it | ||||
| 	err = a.Client.WaitForContainer(containerID, container.WaitConditionNotRunning) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "supervisord init container %s failed to complete", containerID) | ||||
| 	} | ||||
|  | ||||
| 	err = a.Client.RemoveContainer(containerID) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "unable to remove supervisord init container %s", containerID) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // UpdateComponentWithSupervisord updates the devfile component's | ||||
| // 1. command and args with supervisord, if absent | ||||
| // 2. env with ODO_COMMAND_RUN and ODO_COMMAND_RUN_WORKING_DIR, if absent | ||||
| func updateComponentWithSupervisord(comp *versionsCommon.DevfileComponent, runCommand versionsCommon.DevfileCommand, supervisordVolumeName string, hostConfig *container.HostConfig) { | ||||
|  | ||||
| 	// Mount the supervisord volume for the run command container | ||||
| 	if runCommand.Exec.Component == comp.Container.Name { | ||||
| 		utils.AddVolumeToContainer(supervisordVolumeName, common.SupervisordMountPath, hostConfig) | ||||
|  | ||||
| 		if len(comp.Container.Command) == 0 && len(comp.Container.Args) == 0 { | ||||
| 			klog.V(4).Infof("Updating container %v entrypoint with supervisord", comp.Container.Name) | ||||
| 			comp.Container.Command = append(comp.Container.Command, common.SupervisordBinaryPath) | ||||
| 			comp.Container.Args = append(comp.Container.Args, "-c", common.SupervisordConfFile) | ||||
| 		} | ||||
|  | ||||
| 		if !common.IsEnvPresent(comp.Container.Env, common.EnvOdoCommandRun) { | ||||
| 			envName := common.EnvOdoCommandRun | ||||
| 			envValue := runCommand.Exec.CommandLine | ||||
| 			comp.Container.Env = append(comp.Container.Env, versionsCommon.Env{ | ||||
| 				Name:  envName, | ||||
| 				Value: envValue, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		if !common.IsEnvPresent(comp.Container.Env, common.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != "" { | ||||
| 			envName := common.EnvOdoCommandRunWorkingDir | ||||
| 			envValue := runCommand.Exec.WorkingDir | ||||
| 			comp.Container.Env = append(comp.Container.Env, versionsCommon.Env{ | ||||
| 				Name:  envName, | ||||
| 				Value: envValue, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,14 @@ | ||||
| package component | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/docker/go-connections/nat" | ||||
|  | ||||
| 	"github.com/docker/docker/api/types" | ||||
| 	"github.com/docker/docker/api/types/container" | ||||
| 	"github.com/docker/docker/api/types/mount" | ||||
| 	adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" | ||||
| 	devfileParser "github.com/openshift/odo/pkg/devfile/parser" | ||||
| @@ -106,17 +109,18 @@ func TestUpdateComponent(t *testing.T) { | ||||
| 			wantErr:       true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 3: Valid devfile, missing component", | ||||
| 			name: "Case 4: Valid devfile, missing component", | ||||
| 			components: []versionsCommon.DevfileComponent{ | ||||
| 				{ | ||||
| 					Container: &versionsCommon.Container{ | ||||
| 						Name: "fakecomponent", | ||||
| 						Name:  "alias1", | ||||
| 						Image: "someimage", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			componentName: "fakecomponent", | ||||
| 			client:        fakeClient, | ||||
| 			wantErr:       true, | ||||
| 			wantErr:       false, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| @@ -220,7 +224,8 @@ func TestPullAndStartContainer(t *testing.T) { | ||||
| 			} | ||||
|  | ||||
| 			componentAdapter := New(adapterCtx, *tt.client) | ||||
| 			err := componentAdapter.pullAndStartContainer(tt.mounts, testVolumeName, adapterCtx.Devfile.Data.GetAliasedComponents()[0]) | ||||
| 			componentAdapter.projectVolumeName = testVolumeName | ||||
| 			err := componentAdapter.pullAndStartContainer(tt.mounts, adapterCtx.Devfile.Data.GetAliasedComponents()[0]) | ||||
|  | ||||
| 			// Checks for unexpected error cases | ||||
| 			if !tt.wantErr == (err != nil) { | ||||
| @@ -301,7 +306,8 @@ func TestStartContainer(t *testing.T) { | ||||
| 			} | ||||
|  | ||||
| 			componentAdapter := New(adapterCtx, *tt.client) | ||||
| 			err := componentAdapter.startComponent(tt.mounts, testVolumeName, adapterCtx.Devfile.Data.GetAliasedComponents()[0]) | ||||
| 			componentAdapter.projectVolumeName = testVolumeName | ||||
| 			err := componentAdapter.startComponent(tt.mounts, adapterCtx.Devfile.Data.GetAliasedComponents()[0]) | ||||
|  | ||||
| 			// Checks for unexpected error cases | ||||
| 			if !tt.wantErr == (err != nil) { | ||||
| @@ -667,10 +673,527 @@ func TestInitRunContainerSupervisord(t *testing.T) { | ||||
| 			} | ||||
|  | ||||
| 			componentAdapter := New(adapterCtx, *tt.client) | ||||
| 			err := componentAdapter.InitRunContainerSupervisord(tt.component, containers) | ||||
| 			err := componentAdapter.initRunContainerSupervisord(tt.component, containers) | ||||
| 			if !tt.wantErr && err != nil { | ||||
| 				t.Errorf("TestInitRunContainerSupervisord error: unexpected error during init supervisord: %v", err) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestCreateProjectVolumeIfReqd(t *testing.T) { | ||||
| 	fakeClient := lclient.FakeNew() | ||||
| 	fakeErrorClient := lclient.FakeErrorNew() | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name           string | ||||
| 		componentName  string | ||||
| 		client         *lclient.Client | ||||
| 		wantVolumeName string | ||||
| 		wantErr        bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:           "Case 1: Volume does not exist", | ||||
| 			componentName:  "somecomponent", | ||||
| 			client:         fakeClient, | ||||
| 			wantVolumeName: projectSourceVolumeName + "-somecomponent", | ||||
| 			wantErr:        false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Case 2: Volume exist", | ||||
| 			componentName:  "test", | ||||
| 			client:         fakeClient, | ||||
| 			wantVolumeName: projectSourceVolumeName + "-test", | ||||
| 			wantErr:        false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Case 3: More than one project volume exist", | ||||
| 			componentName:  "duplicate", | ||||
| 			client:         fakeClient, | ||||
| 			wantVolumeName: "", | ||||
| 			wantErr:        true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "Case 4: Client error", | ||||
| 			componentName:  "random", | ||||
| 			client:         fakeErrorClient, | ||||
| 			wantVolumeName: "", | ||||
| 			wantErr:        true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			devObj := devfileParser.DevfileObj{ | ||||
| 				Data: testingutil.TestDevfileData{ | ||||
| 					Components: []versionsCommon.DevfileComponent{ | ||||
| 						{ | ||||
| 							Type: versionsCommon.ContainerComponentType, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 			adapterCtx := adaptersCommon.AdapterContext{ | ||||
| 				ComponentName: tt.componentName, | ||||
| 				Devfile:       devObj, | ||||
| 			} | ||||
|  | ||||
| 			componentAdapter := New(adapterCtx, *tt.client) | ||||
| 			volumeName, err := componentAdapter.createProjectVolumeIfReqd() | ||||
| 			if !tt.wantErr && err != nil { | ||||
| 				t.Errorf("TestCreateAndGetProjectVolume error: Unexpected error: %v", err) | ||||
| 			} else if !tt.wantErr && !strings.Contains(volumeName, tt.wantVolumeName) { | ||||
| 				t.Errorf("TestCreateAndGetProjectVolume error: project volume name did not match, expected: %v got: %v", tt.wantVolumeName, volumeName) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestStartBootstrapSupervisordInitContainer(t *testing.T) { | ||||
|  | ||||
| 	supervisordVolumeName := "supervisord" | ||||
| 	componentName := "myComponent" | ||||
|  | ||||
| 	fakeClient := lclient.FakeNew() | ||||
| 	fakeErrorClient := lclient.FakeErrorNew() | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		client  *lclient.Client | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:    "Case 1: Successfully create a bootstrap container", | ||||
| 			client:  fakeClient, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "Case 2: Failed to create a bootstrap container ", | ||||
| 			client:  fakeErrorClient, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			devObj := devfileParser.DevfileObj{ | ||||
| 				Data: testingutil.TestDevfileData{ | ||||
| 					Components: []versionsCommon.DevfileComponent{ | ||||
| 						{ | ||||
| 							Type: versionsCommon.ContainerComponentType, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 			adapterCtx := adaptersCommon.AdapterContext{ | ||||
| 				ComponentName: componentName, | ||||
| 				Devfile:       devObj, | ||||
| 			} | ||||
|  | ||||
| 			componentAdapter := New(adapterCtx, *tt.client) | ||||
| 			err := componentAdapter.startBootstrapSupervisordInitContainer(supervisordVolumeName) | ||||
| 			if !tt.wantErr && err != nil { | ||||
| 				t.Errorf("TestStartBootstrapSupervisordInitContainer: unexpected error got: %v wanted: %v", err, tt.wantErr) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestCreateAndInitSupervisordVolumeIfReqd(t *testing.T) { | ||||
|  | ||||
| 	fakeClient := lclient.FakeNew() | ||||
| 	fakeErrorClient := lclient.FakeErrorNew() | ||||
|  | ||||
| 	componentName := "myComponent" | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		client  *lclient.Client | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:    "Case 1: Successfully create a bootstrap vol and container", | ||||
| 			client:  fakeClient, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "Case 2: Failed to create a bootstrap vol and container ", | ||||
| 			client:  fakeErrorClient, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			devObj := devfileParser.DevfileObj{ | ||||
| 				Data: testingutil.TestDevfileData{ | ||||
| 					Components: []versionsCommon.DevfileComponent{ | ||||
| 						{ | ||||
| 							Type: versionsCommon.ContainerComponentType, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 			adapterCtx := adaptersCommon.AdapterContext{ | ||||
| 				ComponentName: componentName, | ||||
| 				Devfile:       devObj, | ||||
| 			} | ||||
|  | ||||
| 			componentAdapter := New(adapterCtx, *tt.client) | ||||
| 			volName, err := componentAdapter.createAndInitSupervisordVolumeIfReqd(false) | ||||
| 			if !tt.wantErr && err != nil { | ||||
| 				t.Errorf("TestCreateAndInitSupervisordVolume: unexpected error %v, wanted %v", err, tt.wantErr) | ||||
| 			} else if !tt.wantErr && !strings.Contains(volName, adaptersCommon.SupervisordVolumeName+"-"+componentName) { | ||||
| 				t.Errorf("TestCreateAndInitSupervisordVolume: unexpected supervisord vol name, expected: %v got: %v", adaptersCommon.SupervisordVolumeName, volName) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestUpdateComponentWithSupervisord(t *testing.T) { | ||||
|  | ||||
| 	command := "ls -la" | ||||
| 	component := "alias1" | ||||
| 	workDir := "/" | ||||
| 	emptyString := "" | ||||
| 	garbageString := "garbageString" | ||||
| 	supervisordVolumeName := "supervisordVolumeName" | ||||
| 	defaultWorkDirEnv := adaptersCommon.EnvOdoCommandRunWorkingDir | ||||
| 	defaultCommandEnv := adaptersCommon.EnvOdoCommandRun | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name                  string | ||||
| 		commandExecs          []versionsCommon.Exec | ||||
| 		commandName           string | ||||
| 		comp                  versionsCommon.DevfileComponent | ||||
| 		supervisordVolumeName string | ||||
| 		hostConfig            container.HostConfig | ||||
| 		wantHostConfig        container.HostConfig | ||||
| 		wantCommand           []string | ||||
| 		wantArgs              []string | ||||
| 		wantEnv               []versionsCommon.Env | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "Case 1: No component commands, args, env", | ||||
| 			commandExecs: []versionsCommon.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &versionsCommon.Group{ | ||||
| 						Kind: versionsCommon.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: versionsCommon.DevfileComponent{ | ||||
| 				Container: &versionsCommon.Container{ | ||||
| 					Command: []string{}, | ||||
| 					Args:    []string{}, | ||||
| 					Env:     []versionsCommon.Env{}, | ||||
| 					Name:    component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{adaptersCommon.SupervisordBinaryPath}, | ||||
| 			wantArgs:    []string{"-c", adaptersCommon.SupervisordConfFile}, | ||||
| 			wantEnv: []versionsCommon.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: workDir, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: command, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 2: Existing component command and no args, env", | ||||
| 			commandExecs: []versionsCommon.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &versionsCommon.Group{ | ||||
| 						Kind: versionsCommon.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: versionsCommon.DevfileComponent{ | ||||
| 				Container: &versionsCommon.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{}, | ||||
| 					Env:     []versionsCommon.Env{}, | ||||
| 					Name:    component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{}, | ||||
| 			wantEnv: []versionsCommon.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: workDir, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: command, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 3: Existing component command and args and no env", | ||||
| 			commandExecs: []versionsCommon.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &versionsCommon.Group{ | ||||
| 						Kind: versionsCommon.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: versionsCommon.DevfileComponent{ | ||||
| 				Container: &versionsCommon.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{"some", "args"}, | ||||
| 					Env:     []versionsCommon.Env{}, | ||||
| 					Name:    component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{"some", "args"}, | ||||
| 			wantEnv: []versionsCommon.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: workDir, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: command, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 4: Existing component command, args and env", | ||||
| 			commandExecs: []versionsCommon.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &versionsCommon.Group{ | ||||
| 						Kind: versionsCommon.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: versionsCommon.DevfileComponent{ | ||||
| 				Container: &versionsCommon.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{"some", "args"}, | ||||
| 					Env: []versionsCommon.Env{ | ||||
| 						{ | ||||
| 							Name:  defaultWorkDirEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Name:  defaultCommandEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 					}, | ||||
| 					Name: component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{"some", "args"}, | ||||
| 			wantEnv: []versionsCommon.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 5: Existing host config, should append to it", | ||||
| 			commandExecs: []versionsCommon.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &versionsCommon.Group{ | ||||
| 						Kind: versionsCommon.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: versionsCommon.DevfileComponent{ | ||||
| 				Container: &versionsCommon.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{"some", "args"}, | ||||
| 					Env: []versionsCommon.Env{ | ||||
| 						{ | ||||
| 							Name:  defaultWorkDirEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Name:  defaultCommandEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 					}, | ||||
| 					Name: component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: garbageString, | ||||
| 						Target: garbageString, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: garbageString, | ||||
| 						Target: garbageString, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{"some", "args"}, | ||||
| 			wantEnv: []versionsCommon.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			devObj := devfileParser.DevfileObj{ | ||||
| 				Data: testingutil.TestDevfileData{ | ||||
| 					ExecCommands: tt.commandExecs, | ||||
| 					Components: []versionsCommon.DevfileComponent{ | ||||
| 						{ | ||||
| 							Container: &versionsCommon.Container{ | ||||
| 								Name: tt.comp.Container.Name, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 			runCommand, err := adaptersCommon.GetRunCommand(devObj.Data, tt.commandName) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("TestUpdateComponentWithSupervisord: error getting the run command: %v", err) | ||||
| 			} | ||||
|  | ||||
| 			updateComponentWithSupervisord(&tt.comp, runCommand, tt.supervisordVolumeName, &tt.hostConfig) | ||||
|  | ||||
| 			// Check the container host config | ||||
| 			for _, containerHostConfigMount := range tt.hostConfig.Mounts { | ||||
| 				matched := false | ||||
| 				for _, wantHostConfigMount := range tt.wantHostConfig.Mounts { | ||||
| 					if reflect.DeepEqual(wantHostConfigMount, containerHostConfigMount) { | ||||
| 						matched = true | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if !matched { | ||||
| 					t.Errorf("TestUpdateComponentWithSupervisord: host configs source: %v target:%v do not match wanted host config", containerHostConfigMount.Source, containerHostConfigMount.Target) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Check the component command | ||||
| 			if !reflect.DeepEqual(tt.comp.Container.Command, tt.wantCommand) { | ||||
| 				t.Errorf("TestUpdateComponentWithSupervisord: component commands dont match actual: %v wanted: %v", tt.comp.Container.Command, tt.wantCommand) | ||||
| 			} | ||||
|  | ||||
| 			// Check the component args | ||||
| 			if !reflect.DeepEqual(tt.comp.Container.Args, tt.wantArgs) { | ||||
| 				t.Errorf("TestUpdateComponentWithSupervisord: component args dont match actual: %v wanted: %v", tt.comp.Container.Args, tt.wantArgs) | ||||
| 			} | ||||
|  | ||||
| 			// Check the component env | ||||
| 			for _, compEnv := range tt.comp.Container.Env { | ||||
| 				matched := false | ||||
| 				for _, wantEnv := range tt.wantEnv { | ||||
| 					if reflect.DeepEqual(wantEnv, compEnv) { | ||||
| 						matched = true | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if !matched { | ||||
| 					t.Errorf("TestUpdateComponentWithSupervisord: component env dont match env: %v:%v not present in wanted list", compEnv.Name, compEnv.Value) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,37 +5,50 @@ import ( | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/docker/go-connections/nat" | ||||
| 	"k8s.io/klog" | ||||
|  | ||||
| 	"github.com/docker/docker/api/types" | ||||
| 	"github.com/docker/docker/api/types/container" | ||||
| 	"github.com/docker/docker/api/types/mount" | ||||
|  | ||||
| 	adaptersCommon "github.com/openshift/odo/pkg/devfile/adapters/common" | ||||
| 	"github.com/openshift/odo/pkg/devfile/adapters/docker/storage" | ||||
| 	"github.com/openshift/odo/pkg/devfile/parser/data" | ||||
| 	"github.com/openshift/odo/pkg/devfile/parser/data/common" | ||||
| 	"github.com/openshift/odo/pkg/lclient" | ||||
| 	"github.com/openshift/odo/pkg/log" | ||||
| 	"github.com/openshift/odo/pkg/util" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	supervisordVolume = "supervisord" | ||||
| 	projectsVolume    = "projects" | ||||
| 	volume            = "vol" | ||||
| 	// SupervisordVolume is supervisord volume type | ||||
| 	SupervisordVolume = "supervisord" | ||||
|  | ||||
| 	// ProjectsVolume is project source volume type | ||||
| 	ProjectsVolume = "projects" | ||||
| ) | ||||
|  | ||||
| // ComponentExists checks if Docker containers labeled with the specified component name exists | ||||
| func ComponentExists(client lclient.Client, name string) bool { | ||||
| // ComponentExists checks if a component exist | ||||
| // returns true, if the number of containers equals the number of unique devfile components | ||||
| // returns false, if number of containers is zero | ||||
| // returns an error, if number of containers is more than zero but does not equal the number of unique devfile components | ||||
| func ComponentExists(client lclient.Client, data data.DevfileData, name string) (bool, error) { | ||||
| 	containers, err := GetComponentContainers(client, name) | ||||
| 	if err != nil { | ||||
| 		// log the error since this function basically returns a bool | ||||
| 		log.Error(err) | ||||
| 		return false | ||||
| 		return false, errors.Wrapf(err, "unable to get the containers for component %s", name) | ||||
| 	} | ||||
| 	return len(containers) != 0 | ||||
|  | ||||
| 	supportedComponents := adaptersCommon.GetSupportedComponents(data) | ||||
|  | ||||
| 	var componentExists bool | ||||
| 	if len(containers) == 0 { | ||||
| 		componentExists = false | ||||
| 	} else if len(containers) == len(supportedComponents) { | ||||
| 		componentExists = true | ||||
| 	} else if len(containers) > 0 && len(containers) != len(supportedComponents) { | ||||
| 		return false, errors.New(fmt.Sprintf("component %s is in an invalid state, please execute odo delete and retry odo push", name)) | ||||
| 	} | ||||
|  | ||||
| 	return componentExists, nil | ||||
| } | ||||
|  | ||||
| // GetComponentContainers returns a list of the running component containers | ||||
| @@ -147,7 +160,7 @@ func AddVolumeToContainer(volumeName, volumeMount string, hostConfig *container. | ||||
| func GetProjectVolumeLabels(componentName string) map[string]string { | ||||
| 	volumeLabels := map[string]string{ | ||||
| 		"component": componentName, | ||||
| 		"type":      projectsVolume, | ||||
| 		"type":      ProjectsVolume, | ||||
| 	} | ||||
| 	return volumeLabels | ||||
| } | ||||
| @@ -162,14 +175,15 @@ func GetContainerLabels(componentName, alias string) map[string]string { | ||||
| } | ||||
|  | ||||
| // GetSupervisordVolumeLabels returns the label selectors used to retrieve the supervisord volume | ||||
| func GetSupervisordVolumeLabels() map[string]string { | ||||
| func GetSupervisordVolumeLabels(componentName string) map[string]string { | ||||
| 	image := adaptersCommon.GetBootstrapperImage() | ||||
| 	_, _, _, imageTag := util.ParseComponentImageName(image) | ||||
| 	_, imageWithoutTag, _, imageTag := util.ParseComponentImageName(image) | ||||
|  | ||||
| 	supervisordLabels := map[string]string{ | ||||
| 		"name":    adaptersCommon.SupervisordVolumeName, | ||||
| 		"type":    supervisordVolume, | ||||
| 		"version": imageTag, | ||||
| 		"component": componentName, | ||||
| 		"type":      SupervisordVolume, | ||||
| 		"image":     imageWithoutTag, | ||||
| 		"version":   imageTag, | ||||
| 	} | ||||
| 	return supervisordLabels | ||||
| } | ||||
| @@ -202,113 +216,3 @@ func containerHasPort(devfilePort nat.Port, exposedPorts nat.PortSet) bool { | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // UpdateComponentWithSupervisord updates the devfile component's | ||||
| // 1. command and args with supervisord, if absent | ||||
| // 2. env with ODO_COMMAND_RUN and ODO_COMMAND_RUN_WORKING_DIR, if absent | ||||
| func UpdateComponentWithSupervisord(comp *common.DevfileComponent, runCommand common.DevfileCommand, supervisordVolumeName string, hostConfig *container.HostConfig) { | ||||
|  | ||||
| 	// Mount the supervisord volume for the run command container | ||||
| 	if runCommand.Exec.Component == comp.Container.Name { | ||||
| 		AddVolumeToContainer(supervisordVolumeName, adaptersCommon.SupervisordMountPath, hostConfig) | ||||
|  | ||||
| 		if len(comp.Container.Command) == 0 && len(comp.Container.Args) == 0 { | ||||
| 			klog.V(4).Infof("Updating container %v entrypoint with supervisord", comp.Container.Name) | ||||
| 			comp.Container.Command = append(comp.Container.Command, adaptersCommon.SupervisordBinaryPath) | ||||
| 			comp.Container.Args = append(comp.Container.Args, "-c", adaptersCommon.SupervisordConfFile) | ||||
| 		} | ||||
|  | ||||
| 		if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRun) { | ||||
| 			envName := adaptersCommon.EnvOdoCommandRun | ||||
| 			envValue := runCommand.Exec.CommandLine | ||||
| 			comp.Container.Env = append(comp.Container.Env, common.Env{ | ||||
| 				Name:  envName, | ||||
| 				Value: envValue, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		if !adaptersCommon.IsEnvPresent(comp.Container.Env, adaptersCommon.EnvOdoCommandRunWorkingDir) && runCommand.Exec.WorkingDir != "" { | ||||
| 			envName := adaptersCommon.EnvOdoCommandRunWorkingDir | ||||
| 			envValue := runCommand.Exec.WorkingDir | ||||
| 			comp.Container.Env = append(comp.Container.Env, common.Env{ | ||||
| 				Name:  envName, | ||||
| 				Value: envValue, | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateAndInitSupervisordVolume creates the supervisord volume and initializes | ||||
| // it with supervisord bootstrap image - assembly files and supervisord binary | ||||
| func CreateAndInitSupervisordVolume(client lclient.Client) (string, error) { | ||||
| 	log.Info("\nInitialization") | ||||
| 	s := log.Spinner("Initializing the component") | ||||
| 	defer s.End(false) | ||||
|  | ||||
| 	supervisordVolumeName, err := storage.GenerateVolName(adaptersCommon.SupervisordVolumeName, volume) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrapf(err, "unable to generate volume name for supervisord") | ||||
| 	} | ||||
|  | ||||
| 	supervisordLabels := GetSupervisordVolumeLabels() | ||||
| 	_, err = client.CreateVolume(supervisordVolumeName, supervisordLabels) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrapf(err, "Unable to create supervisord volume for component") | ||||
| 	} | ||||
|  | ||||
| 	err = StartBootstrapSupervisordInitContainer(client, supervisordVolumeName) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrapf(err, "Unable to start supervisord container for component") | ||||
| 	} | ||||
| 	s.End(true) | ||||
|  | ||||
| 	return supervisordVolumeName, nil | ||||
| } | ||||
|  | ||||
| // StartBootstrapSupervisordInitContainer pulls the supervisord bootstrap image, mounts the supervisord | ||||
| // volume, starts the bootstrap container and initializes the supervisord volume via its entrypoint | ||||
| func StartBootstrapSupervisordInitContainer(client lclient.Client, supervisordVolumeName string) error { | ||||
| 	supervisordLabels := GetSupervisordVolumeLabels() | ||||
|  | ||||
| 	image := adaptersCommon.GetBootstrapperImage() | ||||
| 	command := []string{"/usr/bin/cp"} | ||||
| 	args := []string{ | ||||
| 		"-r", | ||||
| 		adaptersCommon.OdoInitImageContents, | ||||
| 		adaptersCommon.SupervisordMountPath, | ||||
| 	} | ||||
|  | ||||
| 	var s *log.Status | ||||
| 	if log.IsDebug() { | ||||
| 		s = log.Spinnerf("Pulling image %s", image) | ||||
| 		defer s.End(false) | ||||
| 	} | ||||
|  | ||||
| 	err := client.PullImage(image) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrapf(err, "unable to pull %s image", image) | ||||
| 	} | ||||
| 	if log.IsDebug() { | ||||
| 		s.End(true) | ||||
| 	} | ||||
|  | ||||
| 	containerConfig := client.GenerateContainerConfig(image, command, args, nil, supervisordLabels, nil) | ||||
| 	hostConfig := container.HostConfig{} | ||||
|  | ||||
| 	AddVolumeToContainer(supervisordVolumeName, adaptersCommon.SupervisordMountPath, &hostConfig) | ||||
|  | ||||
| 	// Create the docker container | ||||
| 	if log.IsDebug() { | ||||
| 		s = log.Spinnerf("Starting container for %s", image) | ||||
| 		defer s.End(false) | ||||
| 	} | ||||
| 	err = client.StartContainer(&containerConfig, &hostConfig, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if log.IsDebug() { | ||||
| 		s.End(true) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -26,32 +26,74 @@ func TestComponentExists(t *testing.T) { | ||||
| 		name          string | ||||
| 		componentName string | ||||
| 		client        *lclient.Client | ||||
| 		components    []common.DevfileComponent | ||||
| 		want          bool | ||||
| 		wantErr       bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:          "Case 1: Component exists", | ||||
| 			componentName: "golang", | ||||
| 			client:        fakeClient, | ||||
| 			want:          true, | ||||
| 			components: []common.DevfileComponent{ | ||||
| 				testingutil.GetFakeComponent("alias1"), | ||||
| 				testingutil.GetFakeComponent("alias2"), | ||||
| 			}, | ||||
| 			want:    true, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:          "Case 2: Component doesn't exist", | ||||
| 			componentName: "fakecomponent", | ||||
| 			client:        fakeClient, | ||||
| 			want:          false, | ||||
| 			components: []common.DevfileComponent{ | ||||
| 				testingutil.GetFakeComponent("alias1"), | ||||
| 			}, | ||||
| 			want:    false, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:          "Case 3: Error with docker client", | ||||
| 			componentName: "golang", | ||||
| 			client:        fakeErrorClient, | ||||
| 			components: []common.DevfileComponent{ | ||||
| 				testingutil.GetFakeComponent("alias1"), | ||||
| 			}, | ||||
| 			want:    false, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:          "Case 4: Container and devfile component mismatch", | ||||
| 			componentName: "test", | ||||
| 			client:        fakeClient, | ||||
| 			components:    []common.DevfileComponent{}, | ||||
| 			want:          false, | ||||
| 			wantErr:       true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:          "Case 5: Devfile does not have supported components", | ||||
| 			componentName: "golang", | ||||
| 			client:        fakeClient, | ||||
| 			components: []common.DevfileComponent{ | ||||
| 				{ | ||||
| 					CheEditor: &common.CheEditor{}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want:    false, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			cmpExists := ComponentExists(*tt.client, tt.componentName) | ||||
| 			if tt.want != cmpExists { | ||||
| 			devObj := devfileParser.DevfileObj{ | ||||
| 				Data: testingutil.TestDevfileData{ | ||||
| 					Components: tt.components, | ||||
| 				}, | ||||
| 			} | ||||
| 			cmpExists, err := ComponentExists(*tt.client, devObj.Data, tt.componentName) | ||||
| 			if !tt.wantErr && err != nil { | ||||
| 				t.Errorf("TestComponentExists error, unexpected error - %v", err) | ||||
| 			} else if !tt.wantErr && tt.want != cmpExists { | ||||
| 				t.Errorf("expected %v, wanted %v", cmpExists, tt.want) | ||||
| 			} | ||||
| 		}) | ||||
| @@ -566,7 +608,7 @@ func TestGetProjectVolumeLabels(t *testing.T) { | ||||
| 			componentName: "some-component", | ||||
| 			want: map[string]string{ | ||||
| 				"component": "some-component", | ||||
| 				"type":      "projects", | ||||
| 				"type":      ProjectsVolume, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| @@ -574,7 +616,7 @@ func TestGetProjectVolumeLabels(t *testing.T) { | ||||
| 			componentName: "", | ||||
| 			want: map[string]string{ | ||||
| 				"component": "", | ||||
| 				"type":      "projects", | ||||
| 				"type":      ProjectsVolume, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| @@ -628,25 +670,30 @@ func TestGetContainerLabels(t *testing.T) { | ||||
|  | ||||
| func TestGetSupervisordVolumeLabels(t *testing.T) { | ||||
|  | ||||
| 	componentNameArr := []string{"myComponent1", "myComponent2"} | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name        string | ||||
| 		customImage bool | ||||
| 		want        map[string]string | ||||
| 		name          string | ||||
| 		componentName string | ||||
| 		customImage   bool | ||||
| 		want          map[string]string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:        "Case 1: Default supervisord image", | ||||
| 			customImage: false, | ||||
| 			name:          "Case 1: Default supervisord image", | ||||
| 			componentName: componentNameArr[0], | ||||
| 			customImage:   false, | ||||
| 			want: map[string]string{ | ||||
| 				"name": adaptersCommon.SupervisordVolumeName, | ||||
| 				"type": supervisordVolume, | ||||
| 				"component": componentNameArr[0], | ||||
| 				"type":      SupervisordVolume, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:        "Case 2: Custom supervisord image", | ||||
| 			customImage: true, | ||||
| 			name:          "Case 2: Custom supervisord image", | ||||
| 			componentName: componentNameArr[1], | ||||
| 			customImage:   true, | ||||
| 			want: map[string]string{ | ||||
| 				"name": adaptersCommon.SupervisordVolumeName, | ||||
| 				"type": supervisordVolume, | ||||
| 				"component": componentNameArr[1], | ||||
| 				"type":      SupervisordVolume, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| @@ -656,11 +703,12 @@ func TestGetSupervisordVolumeLabels(t *testing.T) { | ||||
| 				os.Setenv("ODO_BOOTSTRAPPER_IMAGE", "customimage:customtag") | ||||
| 			} | ||||
| 			image := adaptersCommon.GetBootstrapperImage() | ||||
| 			_, _, _, imageTag := util.ParseComponentImageName(image) | ||||
| 			_, imageWithoutTag, _, imageTag := util.ParseComponentImageName(image) | ||||
|  | ||||
| 			tt.want["version"] = imageTag | ||||
| 			tt.want["image"] = imageWithoutTag | ||||
|  | ||||
| 			labels := GetSupervisordVolumeLabels() | ||||
| 			labels := GetSupervisordVolumeLabels(tt.componentName) | ||||
| 			if !reflect.DeepEqual(tt.want, labels) { | ||||
| 				t.Errorf("expected %v, actual %v", tt.want, labels) | ||||
| 			} | ||||
| @@ -669,419 +717,6 @@ func TestGetSupervisordVolumeLabels(t *testing.T) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestUpdateComponentWithSupervisord(t *testing.T) { | ||||
|  | ||||
| 	command := "ls -la" | ||||
| 	component := "alias1" | ||||
| 	workDir := "/" | ||||
| 	emptyString := "" | ||||
| 	garbageString := "garbageString" | ||||
| 	supervisordVolumeName := "supervisordVolumeName" | ||||
| 	defaultWorkDirEnv := adaptersCommon.EnvOdoCommandRunWorkingDir | ||||
| 	defaultCommandEnv := adaptersCommon.EnvOdoCommandRun | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name                  string | ||||
| 		commandExecs          []common.Exec | ||||
| 		commandName           string | ||||
| 		comp                  common.DevfileComponent | ||||
| 		supervisordVolumeName string | ||||
| 		hostConfig            container.HostConfig | ||||
| 		wantHostConfig        container.HostConfig | ||||
| 		wantCommand           []string | ||||
| 		wantArgs              []string | ||||
| 		wantEnv               []common.Env | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "Case 1: No component commands, args, env", | ||||
| 			commandExecs: []common.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &common.Group{ | ||||
| 						Kind: common.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: common.DevfileComponent{ | ||||
| 				Container: &common.Container{ | ||||
| 					Command: []string{}, | ||||
| 					Args:    []string{}, | ||||
| 					Env:     []common.Env{}, | ||||
| 					Name:    component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{adaptersCommon.SupervisordBinaryPath}, | ||||
| 			wantArgs:    []string{"-c", adaptersCommon.SupervisordConfFile}, | ||||
| 			wantEnv: []common.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: workDir, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: command, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 2: Existing component command and no args, env", | ||||
| 			commandExecs: []common.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &common.Group{ | ||||
| 						Kind: common.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: common.DevfileComponent{ | ||||
| 				Container: &common.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{}, | ||||
| 					Env:     []common.Env{}, | ||||
| 					Name:    component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{}, | ||||
| 			wantEnv: []common.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: workDir, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: command, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 3: Existing component command and args and no env", | ||||
| 			commandExecs: []common.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &common.Group{ | ||||
| 						Kind: common.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: common.DevfileComponent{ | ||||
| 				Container: &common.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{"some", "args"}, | ||||
| 					Env:     []common.Env{}, | ||||
| 					Name:    component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{"some", "args"}, | ||||
| 			wantEnv: []common.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: workDir, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: command, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 4: Existing component command, args and env", | ||||
| 			commandExecs: []common.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &common.Group{ | ||||
| 						Kind: common.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: common.DevfileComponent{ | ||||
| 				Container: &common.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{"some", "args"}, | ||||
| 					Env: []common.Env{ | ||||
| 						{ | ||||
| 							Name:  defaultWorkDirEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Name:  defaultCommandEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 					}, | ||||
| 					Name: component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig:            container.HostConfig{}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{"some", "args"}, | ||||
| 			wantEnv: []common.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Case 5: Existing host config, should append to it", | ||||
| 			commandExecs: []common.Exec{ | ||||
| 				{ | ||||
| 					CommandLine: command, | ||||
| 					Component:   component, | ||||
| 					Group: &common.Group{ | ||||
| 						Kind: common.RunCommandGroupType, | ||||
| 					}, | ||||
| 					WorkingDir: workDir, | ||||
| 				}, | ||||
| 			}, | ||||
| 			commandName: emptyString, | ||||
| 			comp: common.DevfileComponent{ | ||||
| 				Container: &common.Container{ | ||||
| 					Command: []string{"some", "command"}, | ||||
| 					Args:    []string{"some", "args"}, | ||||
| 					Env: []common.Env{ | ||||
| 						{ | ||||
| 							Name:  defaultWorkDirEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Name:  defaultCommandEnv, | ||||
| 							Value: garbageString, | ||||
| 						}, | ||||
| 					}, | ||||
| 					Name: component, | ||||
| 				}, | ||||
| 			}, | ||||
| 			supervisordVolumeName: supervisordVolumeName, | ||||
| 			hostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: garbageString, | ||||
| 						Target: garbageString, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantHostConfig: container.HostConfig{ | ||||
| 				Mounts: []mount.Mount{ | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: supervisordVolumeName, | ||||
| 						Target: adaptersCommon.SupervisordMountPath, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Type:   mount.TypeVolume, | ||||
| 						Source: garbageString, | ||||
| 						Target: garbageString, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCommand: []string{"some", "command"}, | ||||
| 			wantArgs:    []string{"some", "args"}, | ||||
| 			wantEnv: []common.Env{ | ||||
| 				{ | ||||
| 					Name:  defaultWorkDirEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name:  defaultCommandEnv, | ||||
| 					Value: garbageString, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			devObj := devfileParser.DevfileObj{ | ||||
| 				Data: testingutil.TestDevfileData{ | ||||
| 					ExecCommands: tt.commandExecs, | ||||
| 					Components: []common.DevfileComponent{ | ||||
| 						{ | ||||
| 							Container: &common.Container{ | ||||
| 								Name: tt.comp.Container.Name, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
|  | ||||
| 			runCommand, err := adaptersCommon.GetRunCommand(devObj.Data, tt.commandName) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("TestUpdateComponentWithSupervisord: error getting the run command") | ||||
| 			} | ||||
|  | ||||
| 			UpdateComponentWithSupervisord(&tt.comp, runCommand, tt.supervisordVolumeName, &tt.hostConfig) | ||||
|  | ||||
| 			// Check the container host config | ||||
| 			for _, containerHostConfigMount := range tt.hostConfig.Mounts { | ||||
| 				matched := false | ||||
| 				for _, wantHostConfigMount := range tt.wantHostConfig.Mounts { | ||||
| 					if reflect.DeepEqual(wantHostConfigMount, containerHostConfigMount) { | ||||
| 						matched = true | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if !matched { | ||||
| 					t.Errorf("TestUpdateComponentWithSupervisord: host configs source: %v target:%v do not match wanted host config", containerHostConfigMount.Source, containerHostConfigMount.Target) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Check the component command | ||||
| 			if !reflect.DeepEqual(tt.comp.Container.Command, tt.wantCommand) { | ||||
| 				t.Errorf("TestUpdateComponentWithSupervisord: component commands dont match actual: %v wanted: %v", tt.comp.Container.Command, tt.wantCommand) | ||||
| 			} | ||||
|  | ||||
| 			// Check the component args | ||||
| 			if !reflect.DeepEqual(tt.comp.Container.Args, tt.wantArgs) { | ||||
| 				t.Errorf("TestUpdateComponentWithSupervisord: component args dont match actual: %v wanted: %v", tt.comp.Container.Args, tt.wantArgs) | ||||
| 			} | ||||
|  | ||||
| 			// Check the component env | ||||
| 			for _, compEnv := range tt.comp.Container.Env { | ||||
| 				matched := false | ||||
| 				for _, wantEnv := range tt.wantEnv { | ||||
| 					if reflect.DeepEqual(wantEnv, compEnv) { | ||||
| 						matched = true | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if !matched { | ||||
| 					t.Errorf("TestUpdateComponentWithSupervisord: component env dont match env: %v:%v not present in wanted list", compEnv.Name, compEnv.Value) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestStartBootstrapSupervisordInitContainer(t *testing.T) { | ||||
|  | ||||
| 	supervisordVolumeName := supervisordVolume | ||||
|  | ||||
| 	fakeClient := lclient.FakeNew() | ||||
| 	fakeErrorClient := lclient.FakeErrorNew() | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		client  *lclient.Client | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:    "Case 1: Successfully create a bootstrap container", | ||||
| 			client:  fakeClient, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "Case 2: Failed to create a bootstrap container ", | ||||
| 			client:  fakeErrorClient, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			err := StartBootstrapSupervisordInitContainer(*tt.client, supervisordVolumeName) | ||||
| 			if !tt.wantErr && err != nil { | ||||
| 				t.Errorf("TestStartBootstrapSupervisordInitContainer: unexpected error got: %v wanted: %v", err, tt.wantErr) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestCreateAndInitSupervisordVolume(t *testing.T) { | ||||
|  | ||||
| 	fakeClient := lclient.FakeNew() | ||||
| 	fakeErrorClient := lclient.FakeErrorNew() | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		client  *lclient.Client | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:    "Case 1: Successfully create a bootstrap vol and container", | ||||
| 			client:  fakeClient, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "Case 2: Failed to create a bootstrap vol and container ", | ||||
| 			client:  fakeErrorClient, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			volName, err := CreateAndInitSupervisordVolume(*tt.client) | ||||
| 			if !tt.wantErr && err != nil { | ||||
| 				t.Logf("TestCreateAndInitSupervisordVolume: unexpected error %v, wanted %v", err, tt.wantErr) | ||||
| 			} else if !tt.wantErr && volName != adaptersCommon.SupervisordVolumeName { | ||||
| 				t.Logf("TestCreateAndInitSupervisordVolume: unexpected supervisord vol name, expected: %v got: %v", adaptersCommon.SupervisordVolumeName, volName) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestGetContainerIDForAlias(t *testing.T) { | ||||
|  | ||||
| 	containers := []types.Container{ | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package lclient | ||||
| import ( | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/docker/docker/api/types" | ||||
| 	"github.com/docker/docker/api/types/container" | ||||
| @@ -53,19 +54,19 @@ func (dc *Client) GetContainerList() ([]types.Container, error) { | ||||
| // containerConfig - configurations for the container itself (image name, command, ports, etc) (if needed) | ||||
| // hostConfig - configurations related to the host (volume mounts, exposed ports, etc) (if needed) | ||||
| // networkingConfig - endpoints to expose (if needed) | ||||
| // Returns an error if the container couldn't be started. | ||||
| func (dc *Client) StartContainer(containerConfig *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) error { | ||||
| // Returns containerID of the started container, an error if the container couldn't be started | ||||
| func (dc *Client) StartContainer(containerConfig *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) (string, error) { | ||||
| 	resp, err := dc.Client.ContainerCreate(dc.Context, containerConfig, hostConfig, networkingConfig, "") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Start the container | ||||
| 	if err := dc.Client.ContainerStart(dc.Context, resp.ID, types.ContainerStartOptions{}); err != nil { | ||||
| 		return err | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| 	return resp.ID, nil | ||||
| } | ||||
|  | ||||
| // RemoveContainer takes in a given container ID and kills it, then removes it. | ||||
| @@ -150,3 +151,22 @@ func (dc *Client) ExtractProjectToComponent(compInfo common.ComponentInfo, targe | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WaitForContainer waits for the container until the condition is reached | ||||
| func (dc *Client) WaitForContainer(containerID string, condition container.WaitCondition) error { | ||||
|  | ||||
| 	containerWaitCh, errCh := dc.Client.ContainerWait(dc.Context, containerID, condition) | ||||
| 	for { | ||||
| 		select { | ||||
| 		case containerWait := <-containerWaitCh: | ||||
| 			if containerWait.StatusCode != 0 { | ||||
| 				return errors.Errorf("error waiting on container %s until condition %s; status code: %v, error message: %v", containerID, string(condition), containerWait.StatusCode, containerWait.Error.Message) | ||||
| 			} | ||||
| 			return nil | ||||
| 		case err := <-errCh: | ||||
| 			return errors.Wrapf(err, "unable to wait on container %s until condition %s", containerID, string(condition)) | ||||
| 		case <-time.After(2 * time.Minute): | ||||
| 			return errors.Errorf("timeout while waiting for container %s to reach condition %s", containerID, string(condition)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -200,6 +200,7 @@ func TestGetContainersList(t *testing.T) { | ||||
| 					Image: "golang", | ||||
| 					Labels: map[string]string{ | ||||
| 						"component": "golang", | ||||
| 						"alias":     "alias1", | ||||
| 						"8080":      "testurl3", | ||||
| 					}, | ||||
| 					HostConfig: container.HostConfig{ | ||||
| @@ -212,6 +213,11 @@ func TestGetContainersList(t *testing.T) { | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					Mounts: []types.MountPoint{ | ||||
| 						{ | ||||
| 							Destination: OdoSourceVolumeMount, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantErr: false, | ||||
| @@ -244,26 +250,31 @@ func TestStartContainer(t *testing.T) { | ||||
|  | ||||
| 	fakeContainer := container.Config{} | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		client  *Client | ||||
| 		wantErr bool | ||||
| 		name            string | ||||
| 		client          *Client | ||||
| 		wantContainerID string | ||||
| 		wantErr         bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:    "Case 1: Successfully start container", | ||||
| 			client:  fakeClient, | ||||
| 			wantErr: false, | ||||
| 			name:            "Case 1: Successfully start container", | ||||
| 			client:          fakeClient, | ||||
| 			wantContainerID: "golang", | ||||
| 			wantErr:         false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "Case 2: Fail to start", | ||||
| 			client:  fakeErrorClient, | ||||
| 			wantErr: true, | ||||
| 			name:            "Case 2: Fail to start", | ||||
| 			client:          fakeErrorClient, | ||||
| 			wantContainerID: "", | ||||
| 			wantErr:         true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			err := tt.client.StartContainer(&fakeContainer, nil, nil) | ||||
| 			containerID, err := tt.client.StartContainer(&fakeContainer, nil, nil) | ||||
| 			if !tt.wantErr == (err != nil) { | ||||
| 				t.Errorf("expected %v, wanted %v", err, tt.wantErr) | ||||
| 				t.Errorf("TestStartContainer error: expected %v, wanted %v", err, tt.wantErr) | ||||
| 			} else if !tt.wantErr && containerID != tt.wantContainerID { | ||||
| 				t.Errorf("TestStartContainer error: container id of start container did not match: got %v, wanted %v", containerID, tt.wantContainerID) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| @@ -409,3 +420,42 @@ func TestExecCMDInContainer(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestWaitForContainer(t *testing.T) { | ||||
| 	fakeClient := FakeNew() | ||||
| 	fakeErrorClient := FakeErrorNew() | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name      string | ||||
| 		client    *Client | ||||
| 		condition container.WaitCondition | ||||
| 		wantErr   bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:      "Case 1: Successfully wait for a condition", | ||||
| 			client:    fakeClient, | ||||
| 			condition: container.WaitConditionNotRunning, | ||||
| 			wantErr:   false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:      "Case 2: Failed to wait for a condition with error channel", | ||||
| 			client:    fakeErrorClient, | ||||
| 			condition: container.WaitConditionNotRunning, | ||||
| 			wantErr:   true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:      "Case 3: Failed to wait for a condition with bad exit code", | ||||
| 			client:    fakeErrorClient, | ||||
| 			condition: container.WaitConditionNextExit, | ||||
| 			wantErr:   true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			err := tt.client.WaitForContainer("id", tt.condition) | ||||
| 			if !tt.wantErr == (err != nil) { | ||||
| 				t.Errorf("got: %v, wanted: %v", err, tt.wantErr) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -80,6 +80,12 @@ var mockContainerList = []types.Container{ | ||||
| 		Labels: map[string]string{ | ||||
| 			"component": "golang", | ||||
| 			"8080":      "testurl3", | ||||
| 			"alias":     "alias1", | ||||
| 		}, | ||||
| 		Mounts: []types.MountPoint{ | ||||
| 			{ | ||||
| 				Destination: OdoSourceVolumeMount, | ||||
| 			}, | ||||
| 		}, | ||||
| 		HostConfig: container.HostConfig{ | ||||
| 			PortBindings: nat.PortMap{ | ||||
| @@ -157,6 +163,15 @@ func (m *mockDockerClient) ContainerInspect(ctx context.Context, containerID str | ||||
|  | ||||
| func (m *mockDockerClient) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) { | ||||
| 	resultC := make(chan container.ContainerWaitOKBody) | ||||
| 	go func() { | ||||
| 		res := container.ContainerWaitOKBody{ | ||||
| 			StatusCode: 0, | ||||
| 			Error: &container.ContainerWaitOKBodyError{ | ||||
| 				Message: "", | ||||
| 			}, | ||||
| 		} | ||||
| 		resultC <- res | ||||
| 	}() | ||||
| 	return resultC, nil | ||||
| } | ||||
|  | ||||
| @@ -195,6 +210,21 @@ func (m *mockDockerClient) VolumeList(ctx context.Context, filter filters.Args) | ||||
| 					"component": "test", | ||||
| 					"type":      "projects", | ||||
| 				}, | ||||
| 				Name: "odo-project-source-test", | ||||
| 			}, | ||||
| 			{ | ||||
| 				Labels: map[string]string{ | ||||
| 					"component": "duplicate", | ||||
| 					"type":      "projects", | ||||
| 				}, | ||||
| 				Name: "odo-project-source-duplicate1", | ||||
| 			}, | ||||
| 			{ | ||||
| 				Labels: map[string]string{ | ||||
| 					"component": "duplicate", | ||||
| 					"type":      "projects", | ||||
| 				}, | ||||
| 				Name: "odo-project-source-duplicate2", | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, nil | ||||
| @@ -246,7 +276,7 @@ var errContainerStart = errors.New("error starting containers") | ||||
| var errContainerStop = errors.New("error stopping container") | ||||
| var errContainerRemove = errors.New("error removing container") | ||||
| var errContainerInspect = errors.New("error inspecting container") | ||||
| var errContainerWait = errors.New("error timeout waiting for container") | ||||
| var errContainerWait = errors.New("error waiting for container") | ||||
| var errDistributionInspect = errors.New("error inspecting distribution") | ||||
| var errVolumeCreate = errors.New("error creating volume") | ||||
| var errVolumeList = errors.New("error listing volume") | ||||
| @@ -289,8 +319,23 @@ func (m *mockDockerErrorClient) ContainerInspect(ctx context.Context, containerI | ||||
| } | ||||
|  | ||||
| func (m *mockDockerErrorClient) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) { | ||||
| 	resultC := make(chan container.ContainerWaitOKBody) | ||||
| 	err := make(chan error) | ||||
| 	err <- errContainerWait | ||||
| 	go func() { | ||||
| 		if condition == container.WaitConditionNextExit { | ||||
| 			res := container.ContainerWaitOKBody{ | ||||
| 				StatusCode: 1, | ||||
| 				Error: &container.ContainerWaitOKBodyError{ | ||||
| 					Message: "bad status code", | ||||
| 				}, | ||||
| 			} | ||||
| 			resultC <- res | ||||
| 		} | ||||
| 		err <- errContainerWait | ||||
| 	}() | ||||
| 	if condition == container.WaitConditionNextExit { | ||||
| 		return resultC, nil | ||||
| 	} | ||||
| 	return nil, err | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -39,6 +39,8 @@ var _ = Describe("odo docker devfile push command tests", func() { | ||||
| 		label := "component=" + cmpName | ||||
| 		dockerClient.StopContainers(label) | ||||
|  | ||||
| 		dockerClient.RemoveVolumesByComponent(cmpName) | ||||
|  | ||||
| 		helper.Chdir(currentWorkingDirectory) | ||||
| 		helper.DeleteDir(context) | ||||
| 		os.Unsetenv("GLOBALODOCONFIG") | ||||
| @@ -83,7 +85,7 @@ var _ = Describe("odo docker devfile push command tests", func() { | ||||
| 			// Verify the volumes got created successfully (and 3 volumes exist: one source and two defined in devfile) | ||||
| 			label := "component=" + cmpName | ||||
| 			volumes := dockerClient.GetVolumesByLabel(label) | ||||
| 			Expect(len(volumes)).To(Equal(3)) | ||||
| 			Expect(len(volumes)).To(Equal(4)) | ||||
| 		}) | ||||
|  | ||||
| 		It("Check that odo push mounts the docker volumes in the container", func() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Maysun J Faisal
					Maysun J Faisal