فهرست منبع

[sqoop] Upgrade for Sqoop for 1.99.4 API: main first cut

vybs 11 سال پیش
والد
کامیت
504fdb9

+ 3 - 3
apps/sqoop/src/sqoop/api/__init__.py

@@ -16,10 +16,10 @@
 # limitations under the License.
 
 from autocomplete import autocomplete
-from connection import get_connections, create_connection, update_connection,\
-                       connection, connections, connection_clone, connection_delete
+from link import get_links, create_link, update_link,\
+                       link, links, link_clone, link_delete
 from connector import get_connectors, connectors, connector
-from framework import framework
+from driver import driver
 from job import get_jobs, create_job, update_job,\
                 job, jobs, job_clone, job_delete,\
                 job_start, job_stop, job_status

+ 5 - 29
apps/sqoop/src/sqoop/api/decorators.py

@@ -38,12 +38,6 @@ LOG = logging.getLogger(__name__)
 
 
 def get_connector_or_exception(exception_class=PopupException):
-  """
-  Decorator ensuring that the user has access to the Connector.
-
-  :param connector_id: Connector ID
-  :returns: Connector
-  """
   def inner(view_func):
     def decorate(request, connector_id, *args, **kwargs):
       try:
@@ -56,32 +50,20 @@ def get_connector_or_exception(exception_class=PopupException):
   return inner
 
 
-def get_connection_or_exception(exception_class=PopupException):
-  """
-  Decorator ensuring that the user has access to the connection.
-
-  :param connection_id: Connection ID
-  :returns: Connection
-  """
+def get_link_or_exception(exception_class=PopupException):
   def inner(view_func):
-    def decorate(request, connection_id, *args, **kwargs):
+    def decorate(request, link_id, *args, **kwargs):
       try:
         c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
-        connection = c.get_connection(int(connection_id))
+        link = c.get_link(int(link_id))
       except RestException, e:
-        handle_rest_exception(e, _('Could not get connection.'))
-      return view_func(request, connection=connection, *args, **kwargs)
+        handle_rest_exception(e, _('Could not get link.'))
+      return view_func(request, link=link, *args, **kwargs)
     return wraps(view_func)(decorate)
   return inner
 
 
 def get_job_or_exception(exception_class=PopupException):
-  """
-  Decorator ensuring that the user has access to the job.
-
-  :param job_id: Job ID
-  :returns: Job
-  """
   def inner(view_func):
     def decorate(request, job_id, *args, **kwargs):
       try:
@@ -95,12 +77,6 @@ def get_job_or_exception(exception_class=PopupException):
 
 
 def get_submission_or_exception(exception_class=PopupException):
-  """
-  Decorator ensuring that the user has access to the submission.
-
-  :param submission_id: Submission ID
-  :returns: Submission
-  """
   def inner(view_func):
     def decorate(request, submission_id, *args, **kwargs):
       try:

+ 5 - 5
apps/sqoop/src/sqoop/api/framework.py → apps/sqoop/src/sqoop/api/driver.py

@@ -31,24 +31,24 @@ from desktop.lib.rest.http_client import RestException
 from exception import handle_rest_exception
 from django.views.decorators.cache import never_cache
 
-__all__ = ['framework']
+__all__ = ['driver']
 
 
 LOG = logging.getLogger(__name__)
 
 @never_cache
-def framework(request):
+def driver(request):
   response = {
     'status': 0,
     'errors': None,
-    'framework': None
+    'driver': None
   }
   if request.method == 'GET':
     try:
       c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
-      response['framework'] = c.get_framework().to_dict()
+      response['driver'] = c.get_driver().to_dict()
     except RestException, e:
-      response.update(handle_rest_exception(e, _('Could not get framework.')))
+      response.update(handle_rest_exception(e, _('Could not get driver.')))
     return HttpResponse(json.dumps(response), mimetype="application/json")
   else:
     raise StructuredException(code="INVALID_METHOD", message=_('GET request required.'), error_code=405)

+ 45 - 45
apps/sqoop/src/sqoop/api/connection.py → apps/sqoop/src/sqoop/api/link.py

@@ -27,132 +27,132 @@ from django.utils.encoding import smart_str
 from django.utils.translation import ugettext as _
 
 from sqoop import client, conf
-from sqoop.client.connection import SqoopConnectionException
-from decorators import get_connection_or_exception
+from sqoop.client.link import SqoopLinkException
+from decorators import get_link_or_exception
 from desktop.lib.exceptions import StructuredException
 from desktop.lib.rest.http_client import RestException
 from exception import handle_rest_exception
 from utils import list_to_dict
 from django.views.decorators.cache import never_cache
 
-__all__ = ['get_connections', 'create_connection', 'update_connection', 'connection', 'connections', 'connection_clone', 'connection_delete']
+__all__ = ['get_links', 'create_link', 'update_link', 'link', 'links', 'link_clone', 'link_delete']
 
 
 LOG = logging.getLogger(__name__)
 
 @never_cache
-def get_connections(request):
+def get_links(request):
   response = {
     'status': 0,
     'errors': None,
-    'connections': []
+    'links': []
   }
   try:
     c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
-    response['connections'] = list_to_dict(c.get_connections())
+    response['links'] = list_to_dict(c.get_links())
   except RestException, e:
-    response.update(handle_rest_exception(e, _('Could not get connections.')))
+    response.update(handle_rest_exception(e, _('Could not get links.')))
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 @never_cache
-def create_connection(request):
+def create_link(request):
   response = {
     'status': 0,
     'errors': None,
-    'connection': None
+    'link': None
   }
 
-  if 'connection' not in request.POST:
-    raise StructuredException(code="INVALID_REQUEST_ERROR", message=_('Error saving connection'), data={'errors': 'Connection is missing.'}, error_code=400)
+  if 'link' not in request.POST:
+    raise StructuredException(code="INVALID_REQUEST_ERROR", message=_('Error saving link'), data={'errors': 'Link is missing.'}, error_code=400)
 
-  d = json.loads(smart_str(request.POST['connection']))
-  conn = client.Connection.from_dict(d)
+  d = json.loads(smart_str(request.POST['link']))
+  link = client.Link.from_dict(d)
 
   try:
     c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
-    response['connection'] = c.create_connection(conn).to_dict()
+    response['link'] = c.create_link(link).to_dict()
   except RestException, e:
-    response.update(handle_rest_exception(e, _('Could not create connection.')))
-  except SqoopConnectionException, e:
+    response.update(handle_rest_exception(e, _('Could not create link.')))
+  except SqoopLinkException, e:
     response['status'] = 100
     response['errors'] = e.to_dict()
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 @never_cache
-def update_connection(request, connection):
+def update_link(request, link):
   response = {
     'status': 0,
     'errors': None,
-    'connection': None
+    'link': None
   }
 
-  if 'connection' not in request.POST:
-    raise StructuredException(code="INVALID_REQUEST_ERROR", message=_('Error saving connection'), data={'errors': 'Connection is missing.'}, error_code=400)
+  if 'link' not in request.POST:
+    raise StructuredException(code="INVALID_REQUEST_ERROR", message=_('Error saving link'), data={'errors': 'Link is missing.'}, error_code=400)
 
-  connection.update_from_dict(json.loads(smart_str(request.POST['connection'])))
+  link.update_from_dict(json.loads(smart_str(request.POST['link'])))
 
   try:
     c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
-    response['connection'] = c.update_connection(connection).to_dict()
+    response['link'] = c.update_link(link).to_dict()
   except RestException, e:
-    response.update(handle_rest_exception(e, _('Could not update connection.')))
-  except SqoopConnectionException, e:
+    response.update(handle_rest_exception(e, _('Could not update link.')))
+  except SqoopLinkException, e:
     response['status'] = 100
     response['errors'] = e.to_dict()
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 @never_cache
-def connections(request):
+def links(request):
   if request.method == 'GET':
-    return get_connections(request)
+    return get_links(request)
   elif request.method == 'POST':
-    return create_connection(request)
+    return create_link(request)
   else:
     raise StructuredException(code="INVALID_METHOD", message=_('GET or POST request required.'), error_code=405)
 
 @never_cache
-@get_connection_or_exception()
-def connection(request, connection):
+@get_link_or_exception()
+def link(request, link):
   response = {
     'status': 0,
     'errors': None,
-    'connection': None
+    'link': None
   }
   if request.method == 'GET':
-    response['connection'] = connection.to_dict()
+    response['link'] = link.to_dict()
     return HttpResponse(json.dumps(response), mimetype="application/json")
   elif request.method == 'POST':
-    return update_connection(request, connection)
+    return update_link(request, link)
   else:
     raise StructuredException(code="INVALID_METHOD", message=_('GET or POST request required.'), error_code=405)
 
 @never_cache
-@get_connection_or_exception()
-def connection_clone(request, connection):
+@get_link_or_exception()
+def link_clone(request, link):
   if request.method != 'POST':
     raise StructuredException(code="INVALID_METHOD", message=_('POST request required.'), error_code=405)
 
   response = {
     'status': 0,
     'errors': None,
-    'connection': None
+    'link': None
   }
 
-  connection.id = -1
-  connection.name = '%s-copy' % connection.name
+  link.id = -1
+  link.name = '%s-copy' % link.name
   try:
     c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
-    response['connection'] = c.create_connection(connection).to_dict()
+    response['link'] = c.create_link(link).to_dict()
   except RestException, e:
-    response.update(handle_rest_exception(e, _('Could not clone connection.')))
-  except SqoopConnectionException, e:
+    response.update(handle_rest_exception(e, _('Could not clone link.')))
+  except SqoopLinkException, e:
     response['status'] = 100
     response['errors'] = e.to_dict()
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 @never_cache
-@get_connection_or_exception()
-def connection_delete(request, connection):
+@get_link_or_exception()
+def link_delete(request, link):
   if request.method != 'POST':
     raise StructuredException(code="INVALID_METHOD", message=_('POST request required.'), error_code=405)
 
@@ -163,10 +163,10 @@ def connection_delete(request, connection):
 
   try:
     c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
-    c.delete_connection(connection)
+    c.delete_link(link)
   except RestException, e:
-    response.update(handle_rest_exception(e, _('Could not delete connection.')))
-  except SqoopConnectionException, e:
+    response.update(handle_rest_exception(e, _('Could not delete link.')))
+  except SqoopLinkException, e:
     response['status'] = 100
     response['errors'] = e.to_dict()
   return HttpResponse(json.dumps(response), mimetype="application/json")

+ 1 - 1
apps/sqoop/src/sqoop/api/submission.py

@@ -45,7 +45,7 @@ def get_submissions(request):
     'errors': None,
     'submissions': []
   }
-  status = request.GET.get('status', 'all').split(',')
+  status = request.GET.get('status', 'submissions').split(',')
   try:
     c = client.SqoopClient(conf.SERVER_URL.get(), request.user.username, request.LANGUAGE_CODE)
     submissions = c.get_submissions()

+ 2 - 2
apps/sqoop/src/sqoop/client/__init__.py

@@ -15,8 +15,8 @@
 # limitations under the License.
 
 from base import SqoopClient
-from connection import Connection
+from link import Link
 from connector import Connector
-from framework import Framework
+from driver import Driver
 from job import Job
 from submission import Submission

+ 74 - 75
apps/sqoop/src/sqoop/client/base.py

@@ -28,9 +28,9 @@ from django.utils.translation import ugettext as _
 from desktop.conf import TIME_ZONE
 from desktop.lib.rest.http_client import HttpClient
 
-from connection import Connection, SqoopConnectionException
+from link import Link, SqoopLinkException
 from connector import Connector
-from framework import Framework
+from driver import Driver
 from job import Job, SqoopJobException
 from submission import Submission, SqoopSubmissionException
 from resource import SqoopResource
@@ -44,9 +44,7 @@ _JSON_CONTENT_TYPE = 'application/json'
 
 
 class SqoopClient(object):
-  """
-  Sqoop client
-  """
+
   STATUS_GOOD = ('FINE', 'ACCEPTABLE')
   STATUS_BAD = ('UNACCEPTABLE', 'FAILURE_ON_SUBMIT')
 
@@ -75,91 +73,90 @@ class SqoopClient(object):
   def get_version(self):
     return self._root.get('version', headers=self.headers)
 
-  def get_framework(self):
-    resp_dict = self._root.get('%s/framework/all' % API_VERSION, headers=self.headers)
-    framework = Framework.from_dict(resp_dict)
-    return framework
+  def get_driver(self):
+    resp_dict = self._root.get('%s/driver' % API_VERSION, headers=self.headers)
+    driver = Driver.from_dict(resp_dict)
+    return driver
 
   def get_connectors(self):
-    resp_dict = self._root.get('%s/connector/all' % API_VERSION, headers=self.headers)
-    connectors = [ Connector.from_dict(connector_dict, resp_dict['resources-connector']) for connector_dict in resp_dict['all'] ]
+    resp_dict = self._root.get('%s/connectors' % API_VERSION, headers=self.headers)
+    connectors = [ Connector.from_dict(connector_dict) for connector_dict in resp_dict['connectors'] ]
     return connectors
 
   def get_connector(self, connector_id):
     resp_dict = self._root.get('%s/connector/%d/' % (API_VERSION, connector_id), headers=self.headers)
-    if resp_dict['all']:
-      return Connector.from_dict(resp_dict['all'][0], resp_dict['resources-connector'])
+    if resp_dict['connector']:
+      return Connector.from_dict(resp_dict['connector'])
     return None
 
-  def get_connections(self):
-    resp_dict = self._root.get('%s/connection/all' % API_VERSION, headers=self.headers)
-    connections = [Connection.from_dict(conn_dict) for conn_dict in resp_dict['all']]
-    return connections
+  def get_links(self):
+    resp_dict = self._root.get('%s/links' % API_VERSION, headers=self.headers)
+    links = [Link.from_dict(link_dict) for link_dict in resp_dict['links']]
+    return links
 
-  def get_connection(self, connection_id):
-    resp_dict = self._root.get('%s/connection/%d/' % (API_VERSION, connection_id), headers=self.headers)
-    if resp_dict['all']:
-      return Connection.from_dict(resp_dict['all'][0])
+  def get_link(self, link_id):
+    resp_dict = self._root.get('%s/link/%d/' % (API_VERSION, link_id), headers=self.headers)
+    if resp_dict['link']:
+      return Link.from_dict(resp_dict['link'][0])
     return None
 
-  def create_connection(self, connection):
-    if not connection.connector:
-      connection.connector = self.get_connectors()[0].con_forms
-    if not connection.framework:
-      connection.framework = self.get_framework().con_forms
-    connection.creation_date = int( round(time.time() * 1000) )
-    connection.update_date = connection.creation_date
-    connection_dict = connection.to_dict()
+  def create_link(self, link):
+    if not link.connector:
+      link.connector = self.get_connectors()[0].link_config
+    if not link.driver:
+      link.driver = self.get_driver().job_config
+    link.creation_date = int( round(time.time() * 1000) )
+    link.update_date = link.creation_date
+    link_dict = link.to_dict()
     request_dict = {
-      'all': [connection_dict]
+      'link': [link_dict]
     }
-    resp = self._root.post('%s/connection/' % API_VERSION, data=json.dumps(request_dict), headers=self.headers)
+    resp = self._root.post('%s/link/' % API_VERSION, data=json.dumps(request_dict), headers=self.headers)
     if 'id' not in resp:
-      raise SqoopConnectionException.from_dict(resp)
-    connection.id = resp['id']
-    return connection
-
-  def update_connection(self, connection):
-    """ Update a connection """
-    if not connection.connector:
-      connection.connector = self.get_connectors()[0].con_forms
-    if not connection.framework:
-      connection.framework = self.get_framework().con_forms
-    connection.updated = int( round(time.time() * 1000) )
-    connection_dict = connection.to_dict()
+      raise SqoopLinkException.from_dict(resp)
+    link.id = resp['id']
+    return link
+
+  def update_link(self, link):
+    if not link.link_config_values:
+      link.link_config_values = self.get_connectors()[0].link_config
+    link.updated = int( round(time.time() * 1000) )
+    link_dict = link.to_dict()
     request_dict = {
-      'all': [connection_dict]
+      'link': [link_dict]
     }
-    resp = self._root.put('%s/connection/%d/' % (API_VERSION, connection.id), data=json.dumps(request_dict), headers=self.headers)
-    if resp['connector']['status'] in SqoopClient.STATUS_BAD or resp['framework']['status'] in SqoopClient.STATUS_BAD:
-      raise SqoopConnectionException.from_dict(resp)
-    return connection
+    resp = self._root.put('%s/link/%d/' % (API_VERSION, link.id), data=json.dumps(request_dict), headers=self.headers)
+    if resp['connector']['status'] in SqoopClient.STATUS_BAD or resp['driver']['status'] in SqoopClient.STATUS_BAD:
+      raise SqoopLinkException.from_dict(resp)
+    return link
 
-  def delete_connection(self, connection):
-    resp = self._root.delete('%s/connection/%d/' % (API_VERSION, connection.id), headers=self.headers)
+  def delete_link(self, link):
+    resp = self._root.delete('%s/link/%d/' % (API_VERSION, link.id), headers=self.headers)
     return None
 
   def get_jobs(self):
-    resp_dict = self._root.get('%s/job/all' % API_VERSION, headers=self.headers)
-    jobs = [Job.from_dict(job_dict) for job_dict in resp_dict['all']]
+    resp_dict = self._root.get('%s/jobs' % API_VERSION, headers=self.headers)
+    jobs = [Job.from_dict(job_dict) for job_dict in resp_dict['jobs']]
     return jobs
 
   def get_job(self, job_id):
     resp_dict = self._root.get('%s/job/%d/' % (API_VERSION, job_id), headers=self.headers)
-    if resp_dict['all']:
-      return Job.from_dict(resp_dict['all'][0])
+    if resp_dict['job']:
+      return Job.from_dict(resp_dict['job'])
     return None
 
   def create_job(self, job):
-    if not job.connector:
-      job.connector = self.get_connectors()[0].job_forms[job.type.upper()]
-    if not job.framework:
-      job.framework = self.get_framework().job_forms[job.type.upper()]
+    if not job.from_config_values:
+      job.from_config_values = self.get_connectors()[0].job_config['FROM']
+    if not job.to_config_values:
+      job.to_config_values = self.get_connectors()[0].job_config['TO']
+    if not job.driver_config_values:
+      job.driver_config_values = self.get_driver().job_config
     job.creation_date = int( round(time.time() * 1000) )
     job.update_date = job.creation_date
     job_dict = job.to_dict()
     request_dict = {
-      'all': [job_dict]
+      'job': [job_dict]
     }
     resp = self._root.post('%s/job/' % API_VERSION, data=json.dumps(request_dict), headers=self.headers)
     if 'id' not in resp:
@@ -168,17 +165,19 @@ class SqoopClient(object):
     return job
 
   def update_job(self, job):
-    if not job.connector:
-      job.connector = self.get_connectors()[0].job_forms[job.type.upper()]
-    if not job.framework:
-      job.framework = self.get_framework().job_forms[job.type.upper()]
+    if not job.from_config_values:
+      job.from_config_values = self.get_connectors()[0].job_config['FROM']
+    if not job.to_config_values:
+      job.to_config_values = self.get_connectors()[0].job_config['TO']
+    if not job.driver_config_values:
+      job.driver_config_values = self.get_driver().job_config
     job.updated = int( round(time.time() * 1000) )
     job_dict = job.to_dict()
     request_dict = {
-      'all': [job_dict]
+      'job': [job_dict]
     }
     resp = self._root.put('%s/job/%d/' % (API_VERSION, job.id), data=json.dumps(request_dict), headers=self.headers)
-    if resp['connector']['status'] in SqoopClient.STATUS_BAD or resp['framework']['status'] in SqoopClient.STATUS_BAD:
+    if resp['connector']['status'] in SqoopClient.STATUS_BAD or resp['driver']['status'] in SqoopClient.STATUS_BAD:
       raise SqoopJobException.from_dict(resp)
     return job
 
@@ -187,22 +186,22 @@ class SqoopClient(object):
     return None
 
   def get_job_status(self, job):
-    resp_dict = self._root.get('%s/submission/action/%d/' % (API_VERSION, job.id), headers=self.headers)
-    return Submission.from_dict(resp_dict['all'][0])
+    resp_dict = self._root.get('%s/job/status/%d/' % (API_VERSION, job.id), headers=self.headers)
+    return Submission.from_dict(resp_dict['submission'])
 
   def start_job(self, job):
-    resp_dict = self._root.post('%s/submission/action/%d/' % (API_VERSION, job.id), headers=self.headers)
-    if resp_dict['all'][0]['status'] in SqoopClient.STATUS_BAD:
-      raise SqoopSubmissionException.from_dict(resp_dict['all'][0])
-    return Submission.from_dict(resp_dict['all'][0])
+    resp_dict = self._root.post('%s/job/start/%d/' % (API_VERSION, job.id), headers=self.headers)
+    if resp_dict['submission'][0]['status'] in SqoopClient.STATUS_BAD:
+      raise SqoopSubmissionException.from_dict(resp_dict['submission'])
+    return Submission.from_dict(resp_dict['submission'])
 
   def stop_job(self, job):
-    resp_dict = self._root.delete('%s/submission/action/%d/' % (API_VERSION, job.id), headers=self.headers)
-    return Submission.from_dict(resp_dict['all'][0])
+    resp_dict = self._root.delete('%s/job/stop/%d/' % (API_VERSION, job.id), headers=self.headers)
+    return Submission.from_dict(resp_dict['submission'])
 
   def get_submissions(self):
-    resp_dict = self._root.get('%s/submission/history/all' % API_VERSION, headers=self.headers)
-    submissions = [Submission.from_dict(submission_dict) for submission_dict in resp_dict['all']]
+    resp_dict = self._root.get('%s/submissions' % API_VERSION, headers=self.headers)
+    submissions = [Submission.from_dict(submission_dict) for submission_dict in resp_dict['submissions']]
     return submissions
 
   def set_user(self, user):

+ 75 - 0
apps/sqoop/src/sqoop/client/config.py

@@ -0,0 +1,75 @@
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+
+from desktop.lib.python_util import force_dict_to_strings
+
+
+class Config(object):
+
+  def __init__(self, id, name, type, inputs=[]):
+    self.id = id
+    self.name = name
+    self.type = type
+    self.inputs = inputs
+
+  @staticmethod
+  def from_dict(config_dict):
+    config_dict['inputs'] = [Input.from_dict(input_dict) for input_dict in config_dict.setdefault('inputs', [])]
+    return Config(**force_dict_to_strings(config_dict))
+
+  def to_dict(self):
+    return {
+      'id': self.id,
+      'type': self.type,
+      'name': self.name,
+      'inputs': [input.to_dict() for input in self.inputs]
+    }
+
+
+class Input(object):
+
+  def __init__(self, id, type, name, value=None, values=None, sensitive=False, size=-1):
+    self.id = id
+    self.type = type
+    self.name = name
+    # can be empty for config objects
+    self.value = value
+    # Not sure why we have values even?
+    self.values = values
+    self.sensitive = sensitive
+    self.size = size
+
+  @staticmethod
+  def from_dict(input_dict):
+    if 'values' in input_dict and isinstance(input_dict['values'], basestring):
+      input_dict['values'] = input_dict['values'].split(',')
+
+    return Input(**force_dict_to_strings(input_dict))
+
+  def to_dict(self):
+    d = {
+      'id': self.id,
+      'type': self.type,
+      'name': self.name,
+      'sensitive': self.sensitive
+    }
+    if self.value:
+      d['value'] = self.value
+    if self.size != -1:
+      d['size'] = self.size
+    return d

+ 0 - 210
apps/sqoop/src/sqoop/client/connection.py

@@ -1,210 +0,0 @@
-# Licensed to Cloudera, Inc. under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  Cloudera, Inc. licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import logging
-
-from desktop.lib.python_util import force_dict_to_strings
-
-from exception import SqoopException
-from form import Form
-
-
-class Connection(object):
-  """
-  Sqoop connection object.
-
-  Example of sqoop connection dictionary received by server: {
-    "id": -1,
-    "updated": 1371245829436,
-    "created": 1371245829436,
-    "name": "test1",
-    "connector": [
-      {
-        "id": 1,
-        "inputs": [
-          {
-            "id": 1,
-            "name": "connection.jdbcDriver",
-            "value": "org.apache.derby.jdbc.EmbeddedDriver",
-            "type": "STRING",
-            "size": 128,
-            "sensitive": false
-          },
-          {
-            "id": 2,
-            "name": "connection.connectionString",
-            "value": "jdbc%3Aderby%3A%2Ftmp%2Ftest",
-            "type": "STRING",
-            "size": 128,
-            "sensitive": false
-          },
-          {
-            "id": 3,
-            "name": "connection.username",
-            "type": "STRING",
-            "size": 40,
-            "sensitive": false
-          },
-          {
-            "id": 4,
-            "name": "connection.password",
-            "type": "STRING",
-            "size": 40,
-            "sensitive": true
-          },
-          {
-            "id": 5,
-            "name": "connection.jdbcProperties",
-            "type": "MAP",
-            "sensitive": false
-          }
-        ],
-        "name": "connection",
-        "type": "CONNECTION"
-      }
-    ],
-    "connector-id": 1,
-    "framework": [
-      {
-        "id": 4,
-        "inputs": [
-          {
-            "id": 16,
-            "name": "security.maxConnections",
-            "type": "INTEGER",
-            "sensitive": false
-          }
-        ],
-        "name": "security",
-        "type": "CONNECTION"
-      }
-    ]
-  }
-
-  Some of the key-value pairs are structured and others are not.
-  For example, every connection will have a name, id, and connector-id key,
-  but the values of the ``connector`` key will vary given the chosen connector.
-  The same is true for the ``framework`` key.
-
-  The connection object will have a single framework component
-  and a single connector, for the moment.
-
-  @see sqoop.client.form for more information on unstructured forms in sqoop.
-  """
-  SKIP = ('id', 'creation_date', 'creation_user', 'update_date', 'update_user')
-
-  def __init__(self, name, connector_id, connector=None, framework=None, enabled=True, creation_user='hue', creation_date=0, update_user='hue', update_date=0, **kwargs):
-    self.id = kwargs.setdefault('id', -1)
-    self.creation_user = creation_user
-    self.creation_date = creation_date
-    self.update_user = update_user
-    self.update_date = update_date
-    self.enabled = enabled
-    self.name = name
-    self.connector_id = connector_id
-    self.connector = connector
-    self.framework = framework
-
-  @staticmethod
-  def from_dict(connection_dict):
-    connection_dict.setdefault('connector', [])
-    connection_dict['connector'] = [ Form.from_dict(con_form_dict) for con_form_dict in connection_dict['connector'] ]
-
-    connection_dict.setdefault('framework', [])
-    connection_dict['framework'] = [ Form.from_dict(framework_form_dict) for framework_form_dict in connection_dict['framework'] ]
-
-    if not 'connector_id' in connection_dict:
-      connection_dict['connector_id'] = connection_dict.setdefault('connector-id', -1)
-
-    if not 'creation_user' in connection_dict:
-      connection_dict['creation_user'] = connection_dict.setdefault('creation-user', 'hue')
-
-    if not 'creation_date' in connection_dict:
-      connection_dict['creation_date'] = connection_dict.setdefault('creation-date', 0)
-
-    if not 'update_user' in connection_dict:
-      connection_dict['update_user'] = connection_dict.setdefault('update-user', 'hue')
-
-    if not 'update_date' in connection_dict:
-      connection_dict['update_date'] = connection_dict.setdefault('update-date', 0)
-
-    return Connection(**force_dict_to_strings(connection_dict))
-
-  def to_dict(self):
-    d = {
-      'id': self.id,
-      'name': self.name,
-      'creation-user': self.creation_user,
-      'creation-date': self.creation_date,
-      'update-user': self.update_user,
-      'update-date': self.update_date,
-      'connector-id': self.connector_id,
-      'connector': [ connector.to_dict() for connector in self.connector ],
-      'framework': [ framework.to_dict() for framework in self.framework ],
-      'enabled': self.enabled
-    }
-    return d
-
-  def update_from_dict(self, connection_dict):
-    self.update(Connection.from_dict(connection_dict))
-
-  def update(self, connection):
-    for key in self.__dict__:
-      if key not in Connection.SKIP:
-        if hasattr(connection, key):
-          setattr(self, key, getattr(connection, key))
-
-
-class SqoopConnectionException(SqoopException):
-  """
-  This is what the server generally responds with:
-  {
-    "connector": {
-      "status": "UNACCEPTABLE",
-      "messages": {
-        "connection": {
-          "message": "Can't connect to the database with given credentials: No suitable driver found for test",
-          "status": "ACCEPTABLE"
-        },
-        "connection.connectionString": {
-          "message": "This do not seem as a valid JDBC URL",
-          "status": "UNACCEPTABLE"
-        }
-      }
-    },
-    "framework": {
-      "status": "FINE",
-      "messages": {}
-    }
-  }
-  """
-  def __init__(self, connector, framework):
-    self.connector = connector
-    self.framework = framework
-
-  @classmethod
-  def from_dict(cls, error_dict):
-    return SqoopConnectionException(**force_dict_to_strings(error_dict))
-
-  def to_dict(self):
-    return {
-      'connector': self.connector,
-      'framework': self.framework
-    }
-
-  def __str__(self):
-    return 'Connector: %s\nFramework: %s\n' % (self.connector, self.framework)
-

+ 23 - 216
apps/sqoop/src/sqoop/client/connector.py

@@ -18,227 +18,34 @@ import logging
 
 from desktop.lib.python_util import force_dict_to_strings
 
-from form import Form
+from config import Config
 
 
 class Connector(object):
-  """
-  Sqoop connector object.
 
-  Example of sqoop connector dictionary received by server: {
-      "id": 1,
-      "name": "generic-jdbc-connector",
-      "class": "org.apache.sqoop.connector.jdbc.GenericJdbcConnector",
-      "job-forms": {
-        "IMPORT": [
-          {
-            "id": 3,
-            "inputs": [
-              {
-                "id": 10,
-                "name": "table.schemaName",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 11,
-                "name": "table.tableName",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 12,
-                "name": "table.sql",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 13,
-                "name": "table.columns",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 14,
-                "name": "table.partitionColumn",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 15,
-                "name": "table.boundaryQuery",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              }
-            ],
-            "name": "table",
-            "type": "CONNECTION"
-          }
-        ],
-        "EXPORT": [
-          {
-            "id": 2,
-            "inputs": [
-              {
-                "id": 6,
-                "name": "table.schemaName",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 7,
-                "name": "table.tableName",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 8,
-                "name": "table.sql",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              },
-              {
-                "id": 9,
-                "name": "table.columns",
-                "type": "STRING",
-                "size": 50,
-                "sensitive": false
-              }
-            ],
-            "name": "table",
-            "type": "CONNECTION"
-          }
-        ]
-      },
-      "con-forms": [
-        {
-          "id": 1,
-          "inputs": [
-            {
-              "id": 1,
-              "name": "connection.jdbcDriver",
-              "type": "STRING",
-              "size": 128,
-              "sensitive": false
-            },
-            {
-              "id": 2,
-              "name": "connection.connectionString",
-              "type": "STRING",
-              "size": 128,
-              "sensitive": false
-            },
-            {
-              "id": 3,
-              "name": "connection.username",
-              "type": "STRING",
-              "size": 40,
-              "sensitive": false
-            },
-            {
-              "id": 4,
-              "name": "connection.password",
-              "type": "STRING",
-              "size": 40,
-              "sensitive": true
-            },
-            {
-              "id": 5,
-              "name": "connection.jdbcProperties",
-              "type": "MAP",
-              "sensitive": false
-            }
-          ],
-          "name": "connection",
-          "type": "CONNECTION"
-        }
-      ],
-      "version": "2.0.0-SNAPSHOT"
-    }
-
-  Some of the key-value pairs are structured and others are not.
-  For example, every connector will have a name, id, version, class,
-  con-forms, and job-forms key, but the values of the ``con-forms``
-  and ``job-forms`` keys will vary given the connector.
-
-  The ``job-forms`` and ``con-forms`` keys hold forms.
-  The ``job-forms`` key will hold 2 sets of forms: IMPORT and EXPORT.
-
-  Connector APIs will also return a resource dictionary: {
-    "1": {
-      "ignored.label": "Ignored",
-      "table.partitionColumn.help": "A specific column for data partition",
-      "table.label": "Database configuration",
-      "table.boundaryQuery.label": "Boundary query",
-      "ignored.help": "This is completely ignored",
-      "ignored.ignored.label": "Ignored",
-      "connection.jdbcProperties.help": "Enter any JDBC properties that should be supplied during the creation of connection.",
-      "table.tableName.help": "Table name to process data in the remote database",
-      "connection.username.help": "Enter the username to be used for connecting to the database.",
-      "connection.jdbcDriver.label": "JDBC Driver Class",
-      "table.help": "You must supply the information requested in order to create a job object.",
-      "table.partitionColumn.label": "Partition column name",
-      "ignored.ignored.help": "This is completely ignored",
-      "table.warehouse.label": "Data warehouse",
-      "table.boundaryQuery.help": "The boundary query for data partition",
-      "connection.username.label": "Username",
-      "connection.jdbcDriver.help": "Enter the fully qualified class name of the JDBC driver that will be used for establishing this connection.",
-      "connection.label": "Connection configuration",
-      "table.columns.label": "Table column names",
-      "connection.password.label": "Password",
-      "table.warehouse.help": "The root directory for data",
-      "table.dataDirectory.label": "Data directory",
-      "table.sql.label": "Table SQL statement",
-      "table.sql.help": "SQL statement to process data in the remote database",
-      "table.schemaName.help": "Schema name to process data in the remote database",
-      "connection.jdbcProperties.label": "JDBC Connection Properties",
-      "table.columns.help": "Specific columns of a table name or a table SQL",
-      "connection.connectionString.help": "Enter the value of JDBC connection string to be used by this connector for creating connections.",
-      "table.schemaName.label": "Schema name",
-      "table.dataDirectory.help": "The sub-directory under warehouse for data",
-      "connection.connectionString.label": "JDBC Connection String",
-      "connection.help": "You must supply the information requested in order to create a connection object.",
-      "connection.password.help": "Enter the password to be used for connecting to the database.",
-      "table.tableName.label": "Table name"
-    }
-  }
-
-  The keys of the dictionary are the IDs of the connector.
-  The keys of each entry are names associated with inputs in connector forms.
-
-  @see sqoop.client.form for more information on unstructured forms in sqoop.
-  """
-  def __init__(self, id, name, version, job_forms, con_forms, resources={}, **kwargs):
+  def __init__(self, id, name, version, link_config, job_config, config_resources={}, **kwargs):
     self.id = id
     self.name = name
     self.version = version
-    self.job_forms = job_forms
-    self.con_forms = con_forms
-    self.resources = resources
+    self.job_config = job_config
+    self.link_config = link_config
+    self.config_resources = config_resources
     setattr(self, 'class', kwargs['class'])
 
   @staticmethod
-  def from_dict(connector_dict, resources_dict={}):
-    connector_dict.setdefault('job-forms', {})
-    connector_dict['job_forms'] = {}
-    if 'IMPORT' in connector_dict['job-forms']:
-      connector_dict['job_forms']['IMPORT'] = [ Form.from_dict(job_form_dict) for job_form_dict in connector_dict['job-forms']['IMPORT'] ]
-    if 'EXPORT' in connector_dict['job-forms']:
-      connector_dict['job_forms']['EXPORT'] = [ Form.from_dict(job_form_dict) for job_form_dict in connector_dict['job-forms']['EXPORT'] ]
+  def from_dict(connector_dict):
+
+    connector_dict.setdefault('link_config', [])
+    connector_dict['link_config'] = [ Config.from_dict(link_config_dict) for link_config_dict in connector_dict['link-config'] ]
 
-    connector_dict.setdefault('con-forms', [])
-    connector_dict['con_forms'] = [ Form.from_dict(con_form_dict) for con_form_dict in connector_dict['con-forms'] ]
+    connector_dict.setdefault('job_config', {})
+    connector_dict['job_config'] = {}
+    if 'FROM' in connector_dict['job-config']:
+      connector_dict['job_config']['FROM'] = [ Config.from_dict(from_config_dict) for from_config_dict in connector_dict['job-config']['FROM'] ]
+    if 'TO' in connector_dict['job-config']:
+      connector_dict['job_config']['TO'] = [ Config.from_dict(to_config_dict) for to_config_dict in connector_dict['job-config']['TO'] ]
 
-    connector_dict['resources'] = resources_dict.setdefault(unicode(connector_dict['id']), {})
+    connector_dict['config_resources'] =  connector_dict['all-config-resources']
 
     return Connector(**force_dict_to_strings(connector_dict))
 
@@ -248,12 +55,12 @@ class Connector(object):
       'name': self.name,
       'version': self.version,
       'class': getattr(self, 'class'),
-      'con-forms': [ con_form.to_dict() for con_form in self.con_forms ],
-      'job-forms': {},
-      'resources': self.resources
+      'link-config': [ link_config.to_dict() for link_config in self.link_config ],
+      'job-config': {},
+      'all-config-resources': self.config_resources
     }
-    if 'IMPORT' in self.job_forms:
-      d['job-forms']['IMPORT'] = [ job_form.to_dict() for job_form in self.job_forms['IMPORT'] ]
-    if 'EXPORT' in self.job_forms:
-      d['job-forms']['EXPORT'] = [ job_form.to_dict() for job_form in self.job_forms['EXPORT'] ]
+    if 'FROM' in self.job_config:
+      d['job-config']['FROM'] = [ job_config.to_dict() for job_config in self.job_config['FROM'] ]
+    if 'TO' in self.job_config:
+      d['job-config']['TO'] = [ job_config.to_dict() for job_config in self.job_config['TO'] ]
     return d

+ 48 - 0
apps/sqoop/src/sqoop/client/driver.py

@@ -0,0 +1,48 @@
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+
+from desktop.lib.python_util import force_dict_to_strings
+
+from config import Config
+
+
+class Driver(object):
+
+  def __init__(self, id, version, job_config=None, config_resources={}, **kwargs):
+    self.id = id
+    self.version = version
+    self.job_config = job_config
+    self.config_resources = config_resources
+
+  @staticmethod
+  def from_dict(driver_dict):
+    driver_dict.setdefault('job_config', {})
+    driver_dict['job_config'] = [ Config.from_dict(job_config_dict) for job_config_dict in driver_dict['job-config']]
+    driver_dict['config_resources'] =  driver_dict['all-config-resources']
+
+    return Driver(**force_dict_to_strings(driver_dict))
+
+  def to_dict(self):
+    d = {
+      'id': self.id,
+      'version': self.version,
+      'job-config': [ job_config.to_dict() for job_config in self.job_config ],
+      'all-config-resources': self.config_resources
+    }
+
+    return d

+ 1 - 1
apps/sqoop/src/sqoop/client/exception.py

@@ -16,7 +16,7 @@
 
 import logging
 
-from form import Form
+from config import Config
 
 
 class SqoopException(Exception):

+ 0 - 147
apps/sqoop/src/sqoop/client/form.py

@@ -1,147 +0,0 @@
-# Licensed to Cloudera, Inc. under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  Cloudera, Inc. licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import logging
-
-from desktop.lib.python_util import force_dict_to_strings
-
-
-class Form(object):
-  """
-  Represents a form in sqoop.
-
-  Example sqoop form dictionary received by server: [
-    {
-      "id": 1,
-      "inputs": [
-        {
-          "id": 1,
-          "name": "connection.jdbcDriver",
-          "value": "org.apache.derby.jdbc.EmbeddedDriver",
-          "type": "STRING",
-          "size": 128,
-          "sensitive": false
-        },
-        {
-          "id": 2,
-          "name": "connection.connectionString",
-          "value": "jdbc%3Aderby%3A%2Ftmp%2Ftest",
-          "type": "STRING",
-          "size": 128,
-          "sensitive": false
-        },
-        {
-          "id": 3,
-          "name": "connection.username",
-          "type": "STRING",
-          "size": 40,
-          "sensitive": false
-        },
-        {
-          "id": 4,
-          "name": "connection.password",
-          "type": "STRING",
-          "size": 40,
-          "sensitive": true
-        },
-        {
-          "id": 5,
-          "name": "connection.jdbcProperties",
-          "type": "MAP",
-          "value": {
-            "key": "value"
-          },
-          "sensitive": false
-        }
-      ],
-      "name": "connection",
-      "type": "CONNECTION"
-    }
-  ],
-
-  These forms are relatively unstructured. They will always have an ID, name, type, and inputs.
-  The number of inputs will vary.
-  Their definitions are dynamically generated from  annotations on classes in sqoop.
-  The ID identifies the form in the sqoop metadata reprository.
-  The ID could vary.
-  The ID is unique per type.
-  """
-  def __init__(self, id, name, type, inputs=[]):
-    self.id = id
-    self.name = name
-    self.type = type
-    self.inputs = inputs
-
-  @staticmethod
-  def from_dict(form_dict):
-    form_dict['inputs'] = [Input.from_dict(input_dict) for input_dict in form_dict.setdefault('inputs', [])]
-    return Form(**force_dict_to_strings(form_dict))
-
-  def to_dict(self):
-    return {
-      'id': self.id,
-      'type': self.type,
-      'name': self.name,
-      'inputs': [input.to_dict() for input in self.inputs]
-    }
-
-
-class Input(object):
-  """
-  Represents an input in a sqoop form.
-
-  Example sqoop input dictionary received by server: {
-    "id": 2,
-    "name": "connection.connectionString",
-    "values": "jdbc%3Aderby%3A%2Ftmp%2Ftest",
-    "type": "STRING",
-    "size": 128,
-    "sensitive": false
-  }
-
-  The ID identifies the input in the sqoop metadata repository.
-  The ID could vary.
-  The ID is unique per type and per form.
-  """
-  def __init__(self, id, type, name, value=None, values=None, sensitive=False, size=-1):
-    self.id = id
-    self.type = type
-    self.name = name
-    self.value = value
-    self.values = values
-    self.sensitive = sensitive
-    self.size = size
-
-  @staticmethod
-  def from_dict(input_dict):
-    if 'values' in input_dict and isinstance(input_dict['values'], basestring):
-      input_dict['values'] = input_dict['values'].split(',')
-    return Input(**force_dict_to_strings(input_dict))
-
-  def to_dict(self):
-    d = {
-      'id': self.id,
-      'type': self.type,
-      'name': self.name,
-      'sensitive': self.sensitive
-    }
-    if self.value:
-      d['value'] = self.value
-    if self.values:
-      d['values'] = ','.join(self.values)
-    if self.size != -1:
-      d['size'] = self.size
-    return d

+ 0 - 199
apps/sqoop/src/sqoop/client/framework.py

@@ -1,199 +0,0 @@
-# Licensed to Cloudera, Inc. under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  Cloudera, Inc. licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import logging
-
-from desktop.lib.python_util import force_dict_to_strings
-
-from form import Form
-
-
-class Framework(object):
-  """
-  Sqoop framework object.
-
-  Example of sqoop framework dictionary received by server: {
-    "id": 1,
-    "resources": {
-      "output.label": "Output configuration",
-      "security.maxConnections.help": "Maximal number of connections that this connection object can use at one point in time",
-      "output.storageType.label": "Storage type",
-      "output.ignored.help": "This value is ignored",
-      "input.label": "Input configuration",
-      "security.help": "You must supply the information requested in order to create a job object.",
-      "output.storageType.help": "Target on Hadoop ecosystem where to store data",
-      "input.inputDirectory.help": "Directory that should be exported",
-      "output.outputFormat.label": "Output format",
-      "output.ignored.label": "Ignored",
-      "output.outputFormat.help": "Format in which data should be serialized",
-      "output.help": "You must supply the information requested in order to get information where you want to store your data.",
-      "throttling.help": "Set throttling boundaries to not overload your systems",
-      "input.inputDirectory.label": "Input directory",
-      "throttling.loaders.label": "Loaders",
-      "input.help": "Specifies information required to get data from Hadoop ecosystem",
-      "throttling.extractors.label": "Extractors",
-      "throttling.extractors.help": "Number of extractors that Sqoop will use",
-      "security.label": "Security related configuration options",
-      "throttling.label": "Throttling resources",
-      "throttling.loaders.help": "Number of loaders that Sqoop will use",
-      "output.outputDirectory.help": "Output directory for final data",
-      "security.maxConnections.label": "Max connections",
-      "output.outputDirectory.label": "Output directory"
-    },
-    "job-forms": {
-      "IMPORT": [
-        {
-          "id": 7,
-          "inputs": [
-            {
-              "id": 20,
-              "values": "HDFS",
-              "name": "output.storageType",
-              "type": "ENUM",
-              "sensitive": false
-            },
-            {
-              "id": 21,
-              "values": "TEXT_FILE,SEQUENCE_FILE",
-              "name": "output.outputFormat",
-              "type": "ENUM",
-              "sensitive": false
-            },
-            {
-              "id": 22,
-              "name": "output.outputDirectory",
-              "type": "STRING",
-              "size": 255,
-              "sensitive": false
-            }
-          ],
-          "name": "output",
-          "type": "CONNECTION"
-        },
-        {
-          "id": 8,
-          "inputs": [
-            {
-              "id": 23,
-              "name": "throttling.extractors",
-              "type": "INTEGER",
-              "sensitive": false
-            },
-            {
-              "id": 24,
-              "name": "throttling.loaders",
-              "type": "INTEGER",
-              "sensitive": false
-            }
-          ],
-          "name": "throttling",
-          "type": "CONNECTION"
-        }
-      ],
-      "EXPORT": [
-        {
-          "id": 5,
-          "inputs": [
-            {
-              "id": 17,
-              "name": "input.inputDirectory",
-              "type": "STRING",
-              "size": 255,
-              "sensitive": false
-            }
-          ],
-          "name": "input",
-          "type": "CONNECTION"
-        },
-        {
-          "id": 6,
-          "inputs": [
-            {
-              "id": 18,
-              "name": "throttling.extractors",
-              "type": "INTEGER",
-              "sensitive": false
-            },
-            {
-              "id": 19,
-              "name": "throttling.loaders",
-              "type": "INTEGER",
-              "sensitive": false
-            }
-          ],
-          "name": "throttling",
-          "type": "CONNECTION"
-        }
-      ]
-    },
-    "con-forms": [
-      {
-        "id": 4,
-        "inputs": [
-          {
-            "id": 16,
-            "name": "security.maxConnections",
-            "type": "INTEGER",
-            "sensitive": false
-          }
-        ],
-        "name": "security",
-        "type": "CONNECTION"
-      }
-    ]
-  }
-
-  The ``job-forms`` and ``con-forms`` keys hold forms.
-  The ``job-forms`` key will hold 2 sets of forms: IMPORT and EXPORT.
-  ``id`` is for look up in metadata repository.
-
-  The framework API will return resource information.
-  The keys are names associated with inputs in the various forms.
-
-  @see sqoop.client.form for more information on unstructured forms in sqoop.
-  """
-  def __init__(self, id, job_forms, con_forms, resources, **kwargs):
-    self.id = id
-    self.job_forms = job_forms
-    self.con_forms = con_forms
-    self.resources = resources
-
-  @staticmethod
-  def from_dict(framework_dict):
-    framework_dict.setdefault('job-forms', {})
-    framework_dict['job_forms'] = {}
-    if 'IMPORT' in framework_dict['job-forms']:
-      framework_dict['job_forms']['IMPORT'] = [ Form.from_dict(job_form_dict) for job_form_dict in framework_dict['job-forms']['IMPORT'] ]
-    if 'EXPORT' in framework_dict['job-forms']:
-      framework_dict['job_forms']['EXPORT'] = [ Form.from_dict(job_form_dict) for job_form_dict in framework_dict['job-forms']['EXPORT'] ]
-
-    framework_dict.setdefault('con-forms', [])
-    framework_dict['con_forms'] = [ Form.from_dict(con_form_dict) for con_form_dict in framework_dict['con-forms'] ]
-
-    return Framework(**force_dict_to_strings(framework_dict))
-
-  def to_dict(self):
-    d = {
-      'id': self.id,
-      'con-forms': [ con_form.to_dict() for con_form in self.con_forms ],
-      'job-forms': {},
-      'resources': self.resources
-    }
-    if 'IMPORT' in self.job_forms:
-      d['job-forms']['IMPORT'] = [ job_form.to_dict() for job_form in self.job_forms['IMPORT'] ]
-    if 'EXPORT' in self.job_forms:
-      d['job-forms']['EXPORT'] = [ job_form.to_dict() for job_form in self.job_forms['EXPORT'] ]
-    return d

+ 42 - 178
apps/sqoop/src/sqoop/client/job.py

@@ -19,169 +19,51 @@ import logging
 from desktop.lib.python_util import force_dict_to_strings
 
 from exception import SqoopException
-from form import Form
+from config import Config
 
 
 class Job(object):
-  """
-  Sqoop job object.
 
-  Example of sqoop job dictionary received by server: {
-    "connection-id": 1,
-    "id": 1,
-    "updated": 1371246055277,
-    "created": 1371246055277,
-    "name": "import1",
-    "connector": [
-      {
-        "id": 3,
-        "inputs": [
-          {
-            "id": 10,
-            "name": "table.schemaName",
-            "type": "STRING",
-            "size": 50,
-            "sensitive": false
-          },
-          {
-            "id": 11,
-            "name": "table.tableName",
-            "value": "derbyDB",
-            "type": "STRING",
-            "size": 50,
-            "sensitive": false
-          },
-          {
-            "id": 12,
-            "name": "table.sql",
-            "type": "STRING",
-            "size": 50,
-            "sensitive": false
-          },
-          {
-            "id": 13,
-            "name": "table.columns",
-            "value": "addr",
-            "type": "STRING",
-            "size": 50,
-            "sensitive": false
-          },
-          {
-            "id": 14,
-            "name": "table.partitionColumn",
-            "value": "num",
-            "type": "STRING",
-            "size": 50,
-            "sensitive": false
-          },
-          {
-            "id": 15,
-            "name": "table.boundaryQuery",
-            "type": "STRING",
-            "size": 50,
-            "sensitive": false
-          }
-        ],
-        "name": "table",
-        "type": "CONNECTION"
-      }
-    ],
-    "connector-id": 1,
-    "type": "IMPORT",
-    "framework": [
-      {
-        "id": 7,
-        "inputs": [
-          {
-            "id": 20,
-            "values": "HDFS",
-            "name": "output.storageType",
-            "value": "HDFS",
-            "type": "ENUM",
-            "sensitive": false
-          },
-          {
-            "id": 21,
-            "values": "TEXT_FILE,SEQUENCE_FILE",
-            "name": "output.outputFormat",
-            "value": "TEXT_FILE",
-            "type": "ENUM",
-            "sensitive": false
-          },
-          {
-            "id": 22,
-            "name": "output.outputDirectory",
-            "value": "%2Ftmp%2Fimport1-out",
-            "type": "STRING",
-            "size": 255,
-            "sensitive": false
-          }
-        ],
-        "name": "output",
-        "type": "CONNECTION"
-      },
-      {
-        "id": 8,
-        "inputs": [
-          {
-            "id": 23,
-            "name": "throttling.extractors",
-            "type": "INTEGER",
-            "sensitive": false
-          },
-          {
-            "id": 24,
-            "name": "throttling.loaders",
-            "type": "INTEGER",
-            "sensitive": false
-          }
-        ],
-        "name": "throttling",
-        "type": "CONNECTION"
-      }
-    ]
-  }
-
-  Some of the key-value pairs are structured and others are not.
-  For example, every job will have a name, id, type, connection-id, and connector-id key,
-  but the values of the ``connector`` and ``connection`` keys
-  will vary given the chosen connector and connection.
-  The same is true for the ``framework`` key.
-
-  The job object will have two framework components
-  and a single connector, for the moment.
-
-  @see sqoop.client.form for more information on unstructured forms in sqoop.
-  """
   SKIP = ('id', 'creation_date', 'creation_user', 'update_date', 'update_user')
 
-  def __init__(self, type, name, connection_id, connector_id, connector=None, framework=None, enabled=True, creation_user='hue', creation_date=0, update_user='hue', update_date=0, **kwargs):
+  def __init__(self, name, from_link_id, to_link_id, from_connector_id, to_connector_id, from_config_values=None, to_config_values=None, driver_config_values=None, enabled=True, creation_user='hue', creation_date=0, update_user='hue', update_date=0, **kwargs):
     self.id = kwargs.setdefault('id', -1)
     self.creation_user = creation_user
     self.creation_date = creation_date
     self.update_user = update_user
     self.update_date = update_date
     self.enabled = enabled
-    self.type = type
     self.name = name
-    self.connection_id = connection_id
-    self.connector_id = connector_id
-    self.connector = connector
-    self.framework = framework
+    self.from_link_id = from_link_id
+    self.to_link_id = to_link_id
+    self.from_connector_id = from_connector_id
+    self.to_connector_id = to_connector_id
+    self.from_config_values = from_config_values
+    self.to_config_values = to_config_values
+    self.driver_config_values = driver_config_values
 
   @staticmethod
   def from_dict(job_dict):
-    job_dict.setdefault('connector', [])
-    job_dict['connector'] = [ Form.from_dict(con_form_dict) for con_form_dict in job_dict['connector'] ]
+    job_dict.setdefault('from_config_values', [])
+    job_dict['from_config_values'] = [ Config.from_dict(from_config_value_dict) for from_config_value_dict in job_dict['from-config-values'] ]
+
+    job_dict.setdefault('to_config_values', [])
+    job_dict['to_config_values'] = [ Config.from_dict(to_config_value_dict) for to_config_value_dict in job_dict['to-config-values'] ]
 
-    job_dict.setdefault('framework', [])
-    job_dict['framework'] = [ Form.from_dict(framework_form_dict) for framework_form_dict in job_dict['framework'] ]
+    job_dict.setdefault('driver_config_values', [])
+    job_dict['driver_config_values'] = [ Config.from_dict(driver_config_value_dict) for driver_config_value_dict in job_dict['driver-config-values'] ]
 
-    if not 'connection_id' in job_dict:
-      job_dict['connection_id'] = job_dict['connection-id']
+    if not 'from_link_id' in job_dict:
+      job_dict['from_link_id'] = job_dict['from-link-id']
 
-    if not 'connector_id' in job_dict:
-      job_dict['connector_id'] = job_dict['connector-id']
+    if not 'to_link_id' in job_dict:
+      job_dict['to_link_id'] = job_dict['to-link-id']
+
+    if not 'from_connector_id' in job_dict:
+      job_dict['from_connector_id'] = job_dict['from-connector-id']
+
+    if not 'to_connector_id' in job_dict:
+      job_dict['to_connector_id'] = job_dict['to-connector-id']
 
     if not 'creation_user' in job_dict:
       job_dict['creation_user'] = job_dict.setdefault('creation-user', 'hue')
@@ -200,16 +82,18 @@ class Job(object):
   def to_dict(self):
     d = {
       'id': self.id,
-      'type': self.type,
       'name': self.name,
       'creation-user': self.creation_user,
       'creation-date': self.creation_date,
       'update-user': self.update_user,
       'update-date': self.update_date,
-      'connection-id': self.connection_id,
-      'connector-id': self.connector_id,
-      'connector': [ connector.to_dict() for connector in self.connector ],
-      'framework': [ framework.to_dict() for framework in self.framework ],
+      'from-link-id': self.from_link_id,
+      'to-link-id': self.to_link_id,
+      'from-connector-id': self.from_connector_id,
+      'to-connector-id': self.to_connector_id,
+      'from-config-values': [ from_config_value.to_dict() for from_config_value in self.from_config_values ],
+      'to-config-values': [ to_config_value.to_dict() for to_config_value in self.to_config_values ],
+      'driver-config-values': [ driver_config_value.to_dict() for driver_config_value in self.driver_config_values ],
       'enabled': self.enabled
     }
     return d
@@ -223,32 +107,11 @@ class Job(object):
         setattr(self, key, getattr(job, key, getattr(self, key)))
 
 class SqoopJobException(SqoopException):
-  """
-  This is what the sqoop server generally responds with:
-  {
-    "connector": {
-      "status": "UNACCEPTABLE",
-      "messages": {
-        "table": {
-          "message": "Either table name or SQL must be specified",
-          "status": "UNACCEPTABLE"
-        }
-      }
-    },
-    "framework": {
-      "status": "UNACCEPTABLE",
-      "messages": {
-        "output.outputDirectory": {
-          "message": "Output directory is empty",
-          "status": "UNACCEPTABLE"
-        }
-      }
-    }
-  }
-  """
-  def __init__(self, connector, framework):
-    self.connector = connector
-    self.framework = framework
+  def __init__(self, from_config_values, to_config_values, driver_config_values):
+    self.link_config_values = from_config_values
+    self.link_config_values = to_config_values
+    self.link_config_values = driver_config_values
+
 
   @classmethod
   def from_dict(cls, error_dict):
@@ -256,10 +119,11 @@ class SqoopJobException(SqoopException):
 
   def to_dict(self):
     return {
-      'connector': self.connector,
-      'framework': self.framework
+      'from-config-values': self.from_config_values,
+      'to-config-values': self.to_config_values,
+      'driver-config-values': self.driver_config_values
     }
 
   def __str__(self):
-    return 'Connector: %s\nFramework: %s\n' % (self.connector, self.framework)
+    return 'From Config Values: %s\nTo Config Values: %s\nDriver Config Values: %s\n' % (self.from_config_values, self.to_config_values, self.driver_config_values)
 

+ 101 - 0
apps/sqoop/src/sqoop/client/link.py

@@ -0,0 +1,101 @@
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+
+from desktop.lib.python_util import force_dict_to_strings
+
+from exception import SqoopException
+from config import Config
+
+
+class Link(object):
+
+  SKIP = ('id', 'creation_date', 'creation_user', 'update_date', 'update_user')
+
+  def __init__(self, name, connector_id, link_config_values=None, enabled=True, creation_user='hue', creation_date=0, update_user='hue', update_date=0, **kwargs):
+    self.id = kwargs.setdefault('id', -1)
+    self.creation_user = creation_user
+    self.creation_date = creation_date
+    self.update_user = update_user
+    self.update_date = update_date
+    self.enabled = enabled
+    self.name = name
+    self.connector_id = connector_id
+    self.link_config_values = link_config_values
+
+  @staticmethod
+  def from_dict(link_dict):
+    link_dict.setdefault('link_config_values', [])
+    link_dict['link_config_values'] = [ Config.from_dict(link_config_value_dict) for link_config_value_dict in link_dict['link-config-values'] ]
+
+    if not 'connector_id' in link_dict:
+      link_dict['connector_id'] = link_dict.setdefault('connector-id', -1)
+
+    if not 'creation_user' in link_dict:
+      link_dict['creation_user'] = link_dict.setdefault('creation-user', 'hue')
+
+    if not 'creation_date' in link_dict:
+      link_dict['creation_date'] = link_dict.setdefault('creation-date', 0)
+
+    if not 'update_user' in link_dict:
+      link_dict['update_user'] = link_dict.setdefault('update-user', 'hue')
+
+    if not 'update_date' in link_dict:
+      link_dict['update_date'] = link_dict.setdefault('update-date', 0)
+
+    return Link(**force_dict_to_strings(link_dict))
+
+  def to_dict(self):
+    d = {
+      'id': self.id,
+      'name': self.name,
+      'creation-user': self.creation_user,
+      'creation-date': self.creation_date,
+      'update-user': self.update_user,
+      'update-date': self.update_date,
+      'connector-id': self.connector_id,
+      'link-config-values': [ connector.to_dict() for connector in self.link_config_values ],
+      'enabled': self.enabled
+    }
+    return d
+
+  def update_from_dict(self, link_dict):
+    self.update(Link.from_dict(link_dict))
+
+  def update(self, link):
+    for key in self.__dict__:
+      if key not in Link.SKIP:
+        if hasattr(link, key):
+          setattr(self, key, getattr(link, key))
+
+
+class SqoopLinkException(SqoopException):
+  def __init__(self, link_config_values):
+    self.link_config_values = link_config_values
+
+  @classmethod
+  def from_dict(cls, error_dict):
+    return SqoopLinkException(**force_dict_to_strings(error_dict))
+
+  def to_dict(self):
+    return {
+      'link-config-values': self.link_config_values
+    }
+
+  def __str__(self):
+    return 'Link Config Values: %s\n' % (self.link_config_values)
+

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 9
apps/sqoop/src/sqoop/client/submission.py


+ 127 - 127
apps/sqoop/src/sqoop/templates/app.mako

@@ -28,21 +28,21 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
       <a title="${_('Create a new job')}" href="#job/new" data-bind="visible: isReady"><i class="fa fa-plus-circle"></i> ${_('New job')}</a>
     </div>
     <div style="margin-top: 4px; margin-right: 40px" class="pull-right">
-      <a title="${_('Manage connections')}" href="#connections" data-bind="visible: isReady"><i class="fa fa-list"></i> ${_('Manage connections')}</a>
+      <a title="${_('Manage links')}" href="#links" data-bind="visible: isReady"><i class="fa fa-list"></i> ${_('Manage links')}</a>
     </div>
     <h4>${_('Sqoop Jobs')}</h4>
     <input id="filter" type="text" class="input-xlarge search-query" placeholder="${_('Search for job name or content')}"  data-bind="visible: isReady">
   </div>
 
-  <div class="top-bar" data-bind="visible:shownSection() == 'connections-list'">
+  <div class="top-bar" data-bind="visible:shownSection() == 'links-list'">
     <div style="margin-top: 4px; margin-right: 40px" class="pull-right">
-      <a title="${_('Create a new connection')}" href="#connection/new" data-bind="visible: isReady"><i class="fa fa-plus-circle"></i> ${_('New connection')}</a>
+      <a title="${_('Create a new link')}" href="#link/new" data-bind="visible: isReady"><i class="fa fa-plus-circle"></i> ${_('New link')}</a>
     </div>
     <div style="margin-top: 4px; margin-right: 40px" class="pull-right">
       <a title="${_('Manage jobs')}" href="#jobs" data-bind="visible: isReady"><i class="fa fa-list"></i> ${_('Manage jobs')}</a>
     </div>
-    <h4>${_('Sqoop Connections')}</h4>
-    <input id="filter" type="text" class="input-xlarge search-query" placeholder="${_('Search for connection name or content')}"  data-bind="visible: isReady">
+    <h4>${_('Sqoop Links')}</h4>
+    <input id="filter" type="text" class="input-xlarge search-query" placeholder="${_('Search for link name or content')}"  data-bind="visible: isReady">
   </div>
 
   <!-- ko if: job -->
@@ -54,9 +54,9 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
     <h4 data-bind="visible: persisted"><a title="${_('Back to jobs list')}" href="#jobs">${_('Sqoop Jobs')}</a> <span class="muted">/</span> <i data-bind="css:{'fa fa-arrow-circle-o-down': type() == 'IMPORT', 'fa fa-upload': type() == 'EXPORT'}"></i> &nbsp;<span data-bind="text: type"></span> <span class="muted" data-bind="editable: name, editableOptions: {'placement': 'right'}"></span></h4>
   </div>
 
-  <div class="top-bar" data-bind="visible:shownSection() == 'connection-editor', with: editConnection">
-    <h4 data-bind="visible: !persisted()"><a title="${_('Back to jobs list')}" href="#jobs">${_('Sqoop Jobs')}</a> <span class="muted">/</span> <a href="#connection/edit-cancel" data-bind="text: name"></a> <span class="muted">/</span> ${_('New Connection')}</h4>
-    <h4 data-bind="visible: persisted()"><a title="${_('Back to jobs list')}" href="#jobs">${_('Sqoop Jobs')}</a> <span class="muted">/</span> <a href="#connection/edit-cancel"><i data-bind="css:{'fa fa-arrow-circle-o-down': $root.job().type() == 'IMPORT', 'fa fa-upload': $root.job().type() == 'EXPORT'}"></i> &nbsp;<span data-bind="text: $root.job().type"></span> <span data-bind="text: $root.job().name"></span></a> <span class="muted">/</span> <span data-bind="text: $root.job().name"></span></h4>
+  <div class="top-bar" data-bind="visible:shownSection() == 'link-editor', with: editLink">
+    <h4 data-bind="visible: !persisted()"><a title="${_('Back to jobs list')}" href="#jobs">${_('Sqoop Jobs')}</a> <span class="muted">/</span> <a href="#link/edit-cancel" data-bind="text: name"></a> <span class="muted">/</span> ${_('New Connection')}</h4>
+    <h4 data-bind="visible: persisted()"><a title="${_('Back to jobs list')}" href="#jobs">${_('Sqoop Jobs')}</a> <span class="muted">/</span> <a href="#link/edit-cancel"><i data-bind="css:{'fa fa-arrow-circle-o-down': $root.job().type() == 'IMPORT', 'fa fa-upload': $root.job().type() == 'EXPORT'}"></i> &nbsp;<span data-bind="text: $root.job().type"></span> <span data-bind="text: $root.job().name"></span></a> <span class="muted">/</span> <span data-bind="text: $root.job().name"></span></h4>
   </div>
   <!-- /ko -->
 </div>
@@ -221,32 +221,32 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
     </div>
   </div>
 
-  <div id="connections" class="row-fluid mainSection hide">
-    <div id="connections-list" class="row-fluid section hide">
+  <div id="links" class="row-fluid mainSection hide">
+    <div id="links-list" class="row-fluid section hide">
       <div class="row-fluid" data-bind="if: isReady">
-        <ul class="major-list" data-bind="foreach: filteredConnections">
-          <li data-bind="routie: 'connection/edit/' + id()" title="${ _('Click to edit') }">
-            <div class="main" data-bind="template: {name: 'connection-list-item'}"></div>
+        <ul class="major-list" data-bind="foreach: filteredLinks">
+          <li data-bind="routie: 'link/edit/' + id()" title="${ _('Click to edit') }">
+            <div class="main" data-bind="template: {name: 'link-list-item'}"></div>
           </li>
         </ul>
-        <div class="card" data-bind="visible: filteredConnections().length == 0">
+        <div class="card" data-bind="visible: filteredLinks().length == 0">
           <div class="span10 offset1 center nojobs">
-            <a href="#connection/new" class="nounderline"><i class="fa fa-plus-circle waiting"></i></a>
-            <h1 class="emptyMessage">${ _('There are currently no connections.') }<br/><a href="#connection/new">${ _('Click here to add one.') }</a></h1>
+            <a href="#link/new" class="nounderline"><i class="fa fa-plus-circle waiting"></i></a>
+            <h1 class="emptyMessage">${ _('There are currently no links.') }<br/><a href="#link/new">${ _('Click here to add one.') }</a></h1>
           </div>
           <div class="clearfix"></div>
         </div>
       </div>
     </div>
 
-    <div id="connection-editor" class="row-fluid section hide" data-bind="with: editConnection">
-      <div id="connection-forms" class="span12">
+    <div id="link-editor" class="row-fluid section hide" data-bind="with: editLink">
+      <div id="link-forms" class="span12">
         <form method="POST" class="form form-horizontal noPadding">
           ${ csrf_token(request) | n,unicode }
           <div class="control-group">
             <label class="control-label">${ _('Name') }</label>
             <div class="controls">
-              <input type="text" name="connection-name" data-bind="value: name">
+              <input type="text" name="link-name" data-bind="value: name">
             </div>
           </div>
           <div class="control-group" data-bind="visible: !persisted()">
@@ -261,14 +261,14 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
               <div data-bind="template: 'connector-' + type().toLowerCase()"></div>
             </div>
           </fieldset>
-          <fieldset data-bind="foreach: framework">
+          <fieldset data-bind="foreach: driver">
             <div data-bind="foreach: inputs">
-              <div data-bind="template: 'framework-' + type().toLowerCase()"></div>
+              <div data-bind="template: 'driver-' + type().toLowerCase()"></div>
             </div>
           </fieldset>
           <div class="form-actions">
-            <a href="#connection/edit-cancel" class="btn">${_('Cancel')}</a>
-            <a href="#connection/save" class="btn btn-primary">${_('Save')}</a>
+            <a href="#link/edit-cancel" class="btn">${_('Cancel')}</a>
+            <a href="#link/save" class="btn btn-primary">${_('Save')}</a>
           </div>
         </form>
       </div>
@@ -301,19 +301,19 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
 </div>
 </script>
 
-<script type="text/html" id="delete-connection-modal">
+<script type="text/html" id="delete-link-modal">
 <div class="modal-header">
   <a href="javascript:void(0);" class="close" data-dismiss="modal">&times;</a>
-  <h3 class="message">${_("Are you sure you'd like to delete this connection?") }</h3>
+  <h3 class="message">${_("Are you sure you'd like to delete this link?") }</h3>
 </div>
 <div class="modal-body"></div>
-<div class="modal-footer" data-bind="if: $root.connection">
+<div class="modal-footer" data-bind="if: $root.link">
   <a class="btn" href="javascript:void(0);" data-dismiss="modal">${_('No')}</a>
-  <a data-bind="routie: {'url': 'connection/delete/' + $root.connection().id(), 'bubble': true}" data-dismiss="modal" class="btn btn-danger" href="javascript:void(0);">${_('Yes, delete it')}</a>
+  <a data-bind="routie: {'url': 'link/delete/' + $root.link().id(), 'bubble': true}" data-dismiss="modal" class="btn btn-danger" href="javascript:void(0);">${_('Yes, delete it')}</a>
 </div>
 </script>
 
-<script type="text/html" id="connection-list-item">
+<script type="text/html" id="link-list-item">
 <h4 style="display: inline-block">
   <i class="fa fa-cog"></i>&nbsp;
   <span data-bind="text: name" class="muted"></span>
@@ -330,7 +330,7 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
   <i class="fa fa-download"></i>&nbsp;
   <span data-bind="text: type"></span>
   <span>${_('from ')}</span>
-  <span data-bind="text: $root.getDatabaseByConnectionId(connection_id())"></span>
+  <span data-bind="text: $root.getDatabaseByConnectionId(link_id())"></span>
   <span>${_('to ')}</span>
   <span data-bind="text: storageType"></span>
   <span data-bind="text: name" class="muted"></span>
@@ -341,7 +341,7 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
   <span>${_('from ')}</span>
   <span data-bind="text: storageType"></span>
   <span>${_('to ')}</span>
-  <span data-bind="text: $root.getDatabaseByConnectionId(connection_id())"></span>
+  <span data-bind="text: $root.getDatabaseByConnectionId(link_id())"></span>
   <span data-bind="text: name" class="muted"></span>
   <!-- /ko -->
 </h4>
@@ -379,12 +379,12 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
 
 <script type="text/html" id="job-editor-begin">
 <fieldset>
-  <div data-bind="template: {'name': 'job-editor-form-error', 'data': {'name': ko.observable('connection')}}" class=""></div>
+  <div data-bind="template: {'name': 'job-editor-form-error', 'data': {'name': ko.observable('link')}}" class=""></div>
 
   <div class="control-group">
     <label class="control-label">${ _('Name') }</label>
     <div class="controls">
-      <input type="text" name="connection-name" data-bind="value: name">
+      <input type="text" name="link-name" data-bind="value: name">
     </div>
   </div>
 
@@ -406,11 +406,11 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
   <div class="control-group">
     <label class="control-label">${ _('Connection') }</label>
     <div class="controls">
-      <select name="connection" class="input-xlarge" data-bind="'options': $root.persistedConnections, 'optionsText': function(item) {return item.name();}, 'value': $root.connection">
+      <select name="link" class="input-xlarge" data-bind="'options': $root.persistedLinks, 'optionsText': function(item) {return item.name();}, 'value': $root.link">
       </select>
-      <!-- ko if: $root.connection() -->
+      <!-- ko if: $root.link() -->
       <div style="display:inline">
-        <a data-bind="routie: 'connection/edit/' + $root.connection().id()" href="javascript:void(0);" class="subbtn" style="margin-left: 5px">
+        <a data-bind="routie: 'link/edit/' + $root.link().id()" href="javascript:void(0);" class="subbtn" style="margin-left: 5px">
           <i class="fa fa-edit"></i> ${_('Edit')}
         </a>
         <a data-bind="click: $root.showDeleteConnectionModal.bind($root)" href="javascript:void(0);" class="subbtn" style="margin-left: 5px">
@@ -419,8 +419,8 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
       </div>
       <!-- /ko -->
       <div class="clearfix"></div>
-      <a data-bind="routie: 'connection/new'" href="javascript:void(0);" style="margin: 5px; display: block">
-        <i class="fa fa-plus"></i> ${_('Add a new connection')}
+      <a data-bind="routie: 'link/new'" href="javascript:void(0);" style="margin: 5px; display: block">
+        <i class="fa fa-plus"></i> ${_('Add a new link')}
       </a>
     </div>
   </div>
@@ -436,21 +436,21 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
 </fieldset>
 </script>
 
-<script type="text/html" id="job-editor-framework">
-<fieldset data-bind="foreach: framework">
+<script type="text/html" id="job-editor-driver">
+<fieldset data-bind="foreach: driver">
   <div data-bind="template: {'name': 'job-editor-form-error'}" class=""></div>
   <div data-bind="foreach: inputs">
-    <div data-bind="template: 'framework-' + type().toLowerCase()"></div>
+    <div data-bind="template: 'driver-' + type().toLowerCase()"></div>
   </div>
 </fieldset>
 </script>
 
-<script type="text/html" id="framework-map">
+<script type="text/html" id="driver-map">
 <div data-bind="css: {
                   warning: name() in $root.warnings(),
                   error: name() in $root.errors()
                 }" class="control-group">
-  <label class="control-label" data-bind="text: $root.label('framework', name())"></label>
+  <label class="control-label" data-bind="text: $root.label('driver', name())"></label>
   <div class="controls">
 
     <table data-bind="visible: value() && value().length > 0" style="margin-bottom: 4px">
@@ -466,7 +466,7 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
           <td>
             <input data-bind="'value': key,
                               'attr': {
-                                'title': $root.help('framework', $parent.name())
+                                'title': $root.help('driver', $parent.name())
                               }" type="text" class="span12 required propKey" />
           </td>
           <td>
@@ -484,49 +484,49 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
 </div>
 </script>
 
-<script type="text/html" id="framework-enum">
+<script type="text/html" id="driver-enum">
 <div data-bind="css:{'control-group': id() != null, warning: name() in $root.warnings(), error: name() in $root.errors()}">
-  <label class="control-label" data-bind="text: $root.label('framework', name())"></label>
+  <label class="control-label" data-bind="text: $root.label('driver', name())"></label>
   <div class="controls">
-    <select class="input-xlarge" data-bind="'options': values, 'value': value, 'optionsCaption': '${ _('Choose...') }', 'attr': { 'name': name, 'title': $root.help('framework', name())}" rel="tooltip">
+    <select class="input-xlarge" data-bind="'options': values, 'value': value, 'optionsCaption': '${ _('Choose...') }', 'attr': { 'name': name, 'title': $root.help('driver', name())}" rel="tooltip">
     </select>
     <span data-bind="template: {'name': 'job-editor-form-field-error'}" class="help-inline"></span>
   </div>
 </div>
 </script>
 
-<script type="text/html" id="framework-string">
+<script type="text/html" id="driver-string">
 <div data-bind="css: {
                   warning: name() in $root.warnings(),
                   error: name() in $root.errors()
                 }" class="control-group">
-  <label class="control-label" data-bind="text: $root.label('framework', name())" rel="tooltip"></label>
+  <label class="control-label" data-bind="text: $root.label('driver', name())" rel="tooltip"></label>
   <div class="controls">
-    <input data-bind="css: {'input-xxlarge': name != '', 'pathChooser': name != '', 'pathChooserExport': $root.job().type() == 'EXPORT'}, value: value, attr: { 'type': (sensitive() ? 'password' : 'text'), 'name': name, 'title': $root.help('framework', name()) }" rel="tooltip"><button class="btn fileChooserBtn" data-bind="click: $root.showFileChooser">..</button>
+    <input data-bind="css: {'input-xxlarge': name != '', 'pathChooser': name != '', 'pathChooserExport': $root.job().type() == 'EXPORT'}, value: value, attr: { 'type': (sensitive() ? 'password' : 'text'), 'name': name, 'title': $root.help('driver', name()) }" rel="tooltip"><button class="btn fileChooserBtn" data-bind="click: $root.showFileChooser">..</button>
     <span data-bind="template: { 'name': 'job-editor-form-field-error' }" class="help-inline"></span>
   </div>
 </div>
 </script>
 
-<script type="text/html" id="framework-integer">
+<script type="text/html" id="driver-integer">
 <div data-bind="css: {
                   warning: name() in $root.warnings(),
                   error: name() in $root.errors()
                 }" class="control-group">
-  <label class="control-label" data-bind="text: $root.label('framework', name())" rel="tooltip"></label>
+  <label class="control-label" data-bind="text: $root.label('driver', name())" rel="tooltip"></label>
   <div class="controls">
-    <input class="input-xlarge" data-bind="value: value, attr: { 'type': (sensitive() ? 'password' : 'text'), 'name': name, 'title': $root.help('framework', name()) }" rel="tooltip">
+    <input class="input-xlarge" data-bind="value: value, attr: { 'type': (sensitive() ? 'password' : 'text'), 'name': name, 'title': $root.help('driver', name()) }" rel="tooltip">
     <span data-bind="template: { 'name': 'job-editor-form-field-error' }" class="help-inline"></span>
   </div>
 </div>
 </script>
 
-<script type="text/html" id="framework-boolean">
+<script type="text/html" id="driver-boolean">
 <div data-bind="css: {
                   warning: name() in $root.warnings(),
                   error: name() in $root.errors()
                 }" class="control-group">
-  <label class="control-label" data-bind="text: $root.label('framework', name())" rel="tooltip"></label>
+  <label class="control-label" data-bind="text: $root.label('driver', name())" rel="tooltip"></label>
   <div class="controls">
     <div class="btn-group inline" data-toggle="buttons-radio" style="display: inline">
       <button data-bind="clickValue: value, attr: {'name': name}" type="button" value="true" class="btn" data-toggle="button">${_('True')}</button>
@@ -649,10 +649,10 @@ ${ commonheader(None, "sqoop", user) | n,unicode }
 <script src="/sqoop/static/js/sqoop.autocomplete.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.utils.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.wizard.js" type="text/javascript" charset="utf-8"></script>
-<script src="/sqoop/static/js/sqoop.forms.js" type="text/javascript" charset="utf-8"></script>
-<script src="/sqoop/static/js/sqoop.framework.js" type="text/javascript" charset="utf-8"></script>
+<script src="/sqoop/static/js/sqoop.configs.js" type="text/javascript" charset="utf-8"></script>
+<script src="/sqoop/static/js/sqoop.driver.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.connectors.js" type="text/javascript" charset="utf-8"></script>
-<script src="/sqoop/static/js/sqoop.connections.js" type="text/javascript" charset="utf-8"></script>
+<script src="/sqoop/static/js/sqoop.links.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.jobs.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.submissions.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.js" type="text/javascript" charset="utf-8"></script>
@@ -677,11 +677,11 @@ viewModel.job.subscribe(function(job) {
         'template': 'job-editor-connector'
       }));
       viewModel.jobWizard.addPage(new wizard.Page({
-        'identifier': 'job-editor-framework',
+        'identifier': 'job-editor-driver',
         'caption': job.type() == 'IMPORT' ? '${_("Step 2: To")}' : '${_("Step 2: From")}',
         'description': '${_("HDFS")}',
         'node': job,
-        'template': 'job-editor-framework'
+        'template': 'job-editor-driver'
       }));
     } else {
       viewModel.jobWizard.addPage(new wizard.Page({
@@ -699,11 +699,11 @@ viewModel.job.subscribe(function(job) {
         'template': 'job-editor-connector'
       }));
       viewModel.jobWizard.addPage(new wizard.Page({
-        'identifier': 'job-editor-framework',
+        'identifier': 'job-editor-driver',
         'caption': '${_("Step 3: To")}',
         'description': '${_("HDFS")}',
         'node': job,
-        'template': 'job-editor-framework'
+        'template': 'job-editor-driver'
       }));
     }
   }
@@ -762,25 +762,25 @@ function handle_form_errors(e, node, options, data) {
 
   if (first_error_component == 'connector') {
     routie('job/edit/wizard/job-editor-connector');
-  } else if (first_error_component == 'framework') {
-    routie('job/edit/wizard/job-editor-framework');
+  } else if (first_error_component == 'driver') {
+    routie('job/edit/wizard/job-editor-driver');
   }
 }
 
-function connection_missing_error(e, node) {
+function link_missing_error(e, node) {
   // Resets save and run btns
   reset_save_buttons();
   viewModel.errors({
-    'connection': [{
+    'link': [{
       'status': 'UNACCEPTABLE',
-      'message': '${_("Please specify a connection.")}'
+      'message': '${_("Please specify a link.")}'
     }]
   });
   viewModel.warnings({});
   routie('job/edit/wizard/job-editor-begin');
 }
 
-$(document).on('connection_error.jobs', function(e, name, options, jqXHR) {
+$(document).on('link_error.jobs', function(e, name, options, jqXHR) {
   viewModel.sqoop_errors.removeAll();
   viewModel.sqoop_errors.push("${ _('Cannot connect to sqoop server.') }");
   routie('error');
@@ -811,8 +811,8 @@ $(document).one('load_fail.job', function() {
 });
 
 $(document).on('save_fail.job', handle_form_errors);
-$(document).on('connection_missing.job', connection_missing_error);
-$(document).on('save_fail.connection', handle_form_errors);
+$(document).on('link_missing.job', link_missing_error);
+$(document).on('save_fail.link', handle_form_errors);
 $(document).on('delete_fail.job', handle_form_errors);
 
 $(document).on('show_section', function(e, section){
@@ -822,8 +822,8 @@ $(document).on('changed.page', function(e, jobWizard) {
   // Autocomplete fields and table name
   $('input[name="table.tableName"]').typeahead({
     'source': function(query, process) {
-      var database = viewModel.connection().database();
-      switch (viewModel.connection().jdbcDriver()) {
+      var database = viewModel.link().database();
+      switch (viewModel.link().jdbcDriver()) {
         case 'com.mysql.jdbc.Driver':
         return autocomplete.tables('mysql', database);
         case 'org.postgresql.Driver':
@@ -836,10 +836,10 @@ $(document).on('changed.page', function(e, jobWizard) {
   });
   $('input[name="table.partitionColumn"],input[name="table.columns"]').typeahead({
     'source': function(query, process) {
-      var database = viewModel.connection().database();
+      var database = viewModel.link().database();
       if (viewModel.job()) {
         var table = viewModel.job().table();
-        switch (viewModel.connection().jdbcDriver()) {
+        switch (viewModel.link().jdbcDriver()) {
           case 'com.mysql.jdbc.Driver':
           return autocomplete.columns('mysql', database, table);
           break;
@@ -860,21 +860,21 @@ $(document).on('changed.page', function(e, jobWizard) {
   });
 });
 $(document).on('shown_section', (function(){
-  var connectionEditorShown = false;
+  var linkEditorShown = false;
   return function(e, section) {
-    if (section == 'connection-editor' && !connectionEditorShown) {
-      connectionEditorShown = true;
-      $('input[name="connection.jdbcDriver"]').typeahead({
+    if (section == 'link-editor' && !linkEditorShown) {
+      linkEditorShown = true;
+      $('input[name="link.jdbcDriver"]').typeahead({
         'source': [
           'com.mysql.jdbc.Driver',
           'org.postgresql.Driver',
           'oracle.jdbc.OracleDriver'
         ]
       });
-      $('input[name="connection.connectionString"]').typeahead({
+      $('input[name="link.linkString"]').typeahead({
         'source': function(query, process) {
           var arr = [];
-          switch (viewModel.connection().jdbcDriver()) {
+          switch (viewModel.link().jdbcDriver()) {
             case 'com.mysql.jdbc.Driver':
             arr = $.map(autocomplete.databases('mysql'), function(value, index) {
               return 'jdbc:mysql://' + host + ':' + port + '/' + value;
@@ -911,7 +911,7 @@ $("#jobs-list tbody").on('click', 'tr', function() {
 });
 
 //// Load all the data
-var framework = new framework.Framework();
+var driver = new driver.Driver();
 (function() {
   function fail(e, options, data) {
     viewModel.isLoading(false);
@@ -932,15 +932,15 @@ var framework = new framework.Framework();
     window.location.hash = 'error';
   }
   $(document).one('load_error.jobs', fail);
-  $(document).one('load_error.framework', fail);
+  $(document).one('load_error.driver', fail);
   $(document).one('load_error.connectors', fail);
-  $(document).one('load_error.connections', fail);
+  $(document).one('load_error.links', fail);
   $(document).one('load_error.submissions', fail);
-  $(document).one('connection_error.jobs', fail);
-  $(document).one('connection_error.framework', fail);
-  $(document).one('connection_error.connectors', fail);
-  $(document).one('connection_error.connections', fail);
-  $(document).one('connection_error.submissions', fail);
+  $(document).one('link_error.jobs', fail);
+  $(document).one('link_error.driver', fail);
+  $(document).one('link_error.connectors', fail);
+  $(document).one('link_error.links', fail);
+  $(document).one('link_error.submissions', fail);
 
   var count = 0;
   function check() {
@@ -951,14 +951,14 @@ var framework = new framework.Framework();
   }
 
   $(document).one('loaded.jobs', check);
-  $(document).one('loaded.framework', check);
+  $(document).one('loaded.driver', check);
   $(document).one('loaded.connectors', check);
-  $(document).one('loaded.connections', check);
+  $(document).one('loaded.links', check);
   $(document).one('loaded.submissions', check);
   $(document).one('loaded.jobs', function() {
-    framework.load();
+    driver.load();
     connectors.fetchConnectors();
-    connections.fetchConnections();
+    links.fetchLinks();
     submissions.fetchSubmissions();
   });
   viewModel.isLoading(true);
@@ -966,13 +966,13 @@ var framework = new framework.Framework();
   jobs.fetchJobs();
 })();
 
-var fetch_connections = function() {
+var fetch_links = function() {
   viewModel.isLoading(true);
-  connections.fetchConnections();
-  $(document).one('loaded.connections', function() {
+  links.fetchLinks();
+  $(document).one('loaded.links', function() {
     viewModel.isLoading(false);
   });
-  $(document).one('load_error.connections', function() {
+  $(document).one('load_error.links', function() {
     viewModel.isLoading(false);
   });
 };
@@ -988,11 +988,11 @@ var fetch_jobs = function() {
   });
 };
 
-$(document).on('saved.connection', fetch_connections);
+$(document).on('saved.link', fetch_links);
 $(document).on('saved.job', fetch_jobs);
-$(document).on('cloned.connection', fetch_connections);
+$(document).on('cloned.link', fetch_links);
 $(document).on('cloned.job', fetch_jobs);
-$(document).on('deleted.connection', fetch_connections);
+$(document).on('deleted.link', fetch_links);
 $(document).on('deleted.job', fetch_jobs);
 
 function enable_save_buttons() {
@@ -1193,68 +1193,68 @@ $(document).ready(function () {
       viewModel.chooseJobById(id);
       routie('job/delete');
     },
-    "connections": function() {
-      showSection("connections", "connections-list");
+    "links": function() {
+      showSection("links", "links-list");
     },
-    "connection/edit": function() {
-      // if (viewModel.connection()) {
+    "link/edit": function() {
+      // if (viewModel.link()) {
       //   routie('')
       // }
-      showSection("connections", "connection-editor");
+      showSection("links", "link-editor");
       $("*[rel=tooltip]").tooltip({
         placement: 'right'
       });
     },
-    "connection/edit/:id": function(id) {
-      viewModel.chooseConnectionById(id);
-      showSection("connections", "connection-editor");
+    "link/edit/:id": function(id) {
+      viewModel.chooseLinkById(id);
+      showSection("links", "link-editor");
       $("*[rel=tooltip]").tooltip({
         placement: 'right'
       });
     },
-    "connection/edit-cancel": function() {
-      if (viewModel.connection() && !viewModel.connection().persisted()) {
-        viewModel.connections.pop();
+    "link/edit-cancel": function() {
+      if (viewModel.link() && !viewModel.link().persisted()) {
+        viewModel.links.pop();
       }
       // routie('job/edit');
       window.history.go(-2);
     },
-    "connection/new": function() {
+    "link/new": function() {
       $(window).one('hashchange', function() {
         viewModel.newConnection();
-        routie('connection/edit');
+        routie('link/edit');
       });
       window.history.back();
     },
-    "connection/save": function() {
+    "link/save": function() {
       viewModel.saveConnection();
-      $(document).one('saved.connection', function(){
+      $(document).one('saved.link', function(){
         routie('job/edit');
       });
-      $(document).one('save_fail.connection', function(){
-        routie('connection/edit');
+      $(document).one('save_fail.link', function(){
+        routie('link/edit');
       });
     },
-    "connection/copy": function() {
-      if (viewModel.connection()) {
-        viewModel.connection().clone();
+    "link/copy": function() {
+      if (viewModel.link()) {
+        viewModel.link().clone();
       }
       routie('job/edit');
     },
-    "connection/delete": function() {
-      if (viewModel.connection()) {
-        viewModel.connection().delete();
-        $(document).one('deleted.connection', function(){
+    "link/delete": function() {
+      if (viewModel.link()) {
+        viewModel.link().delete();
+        $(document).one('deleted.link', function(){
           routie('job/edit');
         });
       } else {
         routie('job/edit');
       }
     },
-    "connection/delete/:id": function(id) {
-      viewModel.chooseConnectionById(id);
-      viewModel.connection().delete();
-      $(document).one('deleted.connection', function(){
+    "link/delete/:id": function(id) {
+      viewModel.chooseLinkById(id);
+      viewModel.link().delete();
+      $(document).one('deleted.link', function(){
         routie('job/edit');
       });
     }
@@ -1271,4 +1271,4 @@ $(document).ready(function () {
 
 </script>
 
-${ commonfooter(messages) | n,unicode }
+${ commonfooter(messages) | n,unicode }

+ 102 - 87
apps/sqoop/src/sqoop/tests.py

@@ -16,89 +16,92 @@
 
 import logging
 
-from sqoop.client.connection import Connection
+from sqoop.client.link import Link
 from sqoop.client.job import Job
 from sqoop.test_base import SqoopServerProvider
 
 from nose.tools import assert_true, assert_equal
+from nose.plugins.skip import SkipTest
 
 
 LOG = logging.getLogger(__name__)
 
 
-CONNECTION_FORM_VALUES = {
-  'connection.jdbcDriver': 'org.apache.derby.jdbc.EmbeddedDriver',
-  'connection.connectionString': 'jdbc%3Aderby%3A%2Ftmp%2Ftest',
-  'connection.username': 'abe',
-  'connection.password': 'test',
-  'connection.jdbcProperties': None
+LINK_CONFIG_VALUES = {
+  'linkConfig.jdbcDriver': 'org.apache.derby.jdbc.EmbeddedDriver',
+  'linkConfig.String': 'jdbc%3Aderby%3A%2Ftmp%2Ftest',
+  'linkConfig.username': 'abe',
+  'linkConfig.password': 'test',
+  'linkConfig.jdbcProperties': None
 }
 
-JOB_FORM_VALUES = {
-  'table.schemaName': None,
-  'table.tableName': 'test',
-  'table.sql': None,
-  'table.columns': 'name',
-  'table.partitionColumn': 'id',
-  'table.boundaryQuery': None,
-  'table.partitionColumnNull': None
+FROM_JOB_CONFIG_VALUES = {
+  'fromJobConfig.schemaName': None,
+  'fromJobConfig.tableName': 'test',
+  'fromJobConfig.sql': None,
+  'fromJobConfig.columns': 'name',
+  'fromJobConfig.partitionColumn': 'id',
+  'fromJobConfig.boundaryQuery': None,
+  'fromJobConfig.allowNullValueInPartitionColumn': None
 }
 
-FRAMEWORK_FORM_VALUES = {
-  'output.outputFormat': 'TEXT_FILE',
-  'output.outputDirectory': '/tmp/test.out',
-  'output.storageType': 'HDFS',
-  'throttling.extractors': None,
-  'throttling.loaders': None,
-  'security.maxConnections': None
+TO_JOB_CONFIG_VALUES = {
+  'toJobConfig.outputFormat': 'TEXT_FILE',
+  'toJobConfig.outputDirectory': '/tmp/test.out',
+  'toJobConfig.storageType': 'HDFS'
+}
+
+DRIVER_CONFIG_VALUES = {
+  'throttlingConfig.numExtractor': '3',
+  'throttlingConfig.numLoaders': '3'
 }
 
 class TestSqoopServerBase(SqoopServerProvider):
-  def create_connection(self, name='test1', connector_id=1):
-    conn = Connection(name, connector_id)
-    conn.framework = self.client.get_framework().con_forms
-    conn.connector = self.client.get_connectors()[0].con_forms
-
-    for _connector in conn.connector:
-      for _input in _connector.inputs:
-        if _input.name not in CONNECTION_FORM_VALUES:
-          LOG.warning("Connection input mapping %s does not exist. Maybe it's new?" % _input.name)
-        elif CONNECTION_FORM_VALUES[_input.name]:
-          _input.value = CONNECTION_FORM_VALUES[_input.name]
-
-    for _framework in conn.framework:
-      for _input in _framework.inputs:
-        if _input.name not in FRAMEWORK_FORM_VALUES:
-          LOG.warning("Framework input mapping %s does not exist. Maybe it's new?" % _input.name)
-        elif FRAMEWORK_FORM_VALUES[_input.name]:
-          _input.value = FRAMEWORK_FORM_VALUES[_input.name]
-
-    return self.client.create_connection(conn)
-
-  def create_job(self, _type="IMPORT", name="test1", connection_id=1, connector_id=1):
-    job = Job(_type, name, connection_id, connector_id)
-    job.framework = self.client.get_framework().job_forms[_type]
-    job.connector = self.client.get_connectors()[0].job_forms[_type]
-
-    for _connector in job.connector:
-      for _input in _connector.inputs:
-        if _input.name not in JOB_FORM_VALUES:
-          LOG.warning("Job input mapping %s does not exist. Maybe it's new?" % _input.name)
-        elif JOB_FORM_VALUES[_input.name]:
-          _input.value = JOB_FORM_VALUES[_input.name]
-
-    for _framework in job.framework:
-      for _input in _framework.inputs:
-        if _input.name not in FRAMEWORK_FORM_VALUES:
-          LOG.warning("Framework input mapping %s does not exist. Maybe it's new?" % _input.name)
-        elif FRAMEWORK_FORM_VALUES[_input.name]:
-          _input.value = FRAMEWORK_FORM_VALUES[_input.name]
+  def create_link(self, name='test1', connector_id=1):
+    link = Link(name, connector_id)
+    link.linkConfig = self.client.get_connectors()[0].link_config
+
+    for _config in link.linkConfig:
+      for _input in _config.inputs:
+        if _input.name not in LINK_CONFIG_VALUES:
+          LOG.warning("Link config input mapping %s does not exist. Maybe it's new?" % _input.name)
+        elif LINK_CONFIG_VALUES[_input.name]:
+          _input.value = LINK_CONFIG_VALUES[_input.name]
+
+    return self.client.create_link(link)
+
+  def create_job(self, name="test1", from_link_id=1, to_link_id=2, from_connector_id=1, to_connector_id=2):
+    job = Job( name, from_link_id, to_link_id, from_connector_id, to_connector_id)
+    job.driver_config = self.client.get_driver().job_config
+    job.from_config = self.client.get_connectors()[0].job_config['FROM']
+    job.to_config = self.client.get_connectors()[0].job_config['TO']
+
+    for _from_config in job.from_config:
+        for _input in _from_config.inputs:
+            if _input.name not in FROM_JOB_CONFIG_VALUES:
+                LOG.warning("From Job config input mapping %s does not exist. Maybe it's new?" % _input.name)
+            elif FROM_JOB_CONFIG_VALUES[_input.name]:
+                _input.value = FROM_JOB_CONFIG_VALUES[_input.name]
+
+    for _to_config in job.to_config:
+        for _input in _to_config.inputs:
+            if _input.name not in TO_JOB_CONFIG_VALUES:
+                LOG.warning("To Job config input mapping. Maybe it's new?" % _input.name)
+            elif TO_JOB_CONFIG_VALUES[_input.name]:
+                _input.value = TO_JOB_CONFIG_VALUES[_input.name]
+
+    for _driver_config in job.driver_config:
+        for _input in _driver_config.inputs:
+            if _input.name not in DRIVER_CONFIG_VALUES:
+                LOG.warning("Driver Job config input mapping. Maybe it's new?" % _input.name)
+            elif DRIVER_CONFIG_VALUES[_input.name]:
+                _input.value = DRIVER_CONFIG_VALUES[_input.name]
 
     return self.client.create_job(job)
 
   def delete_sqoop_object(self, obj):
-    if isinstance(obj, Connection):
-      self.client.delete_connection(obj)
+    if isinstance(obj, Link):
+      self.client.delete_link(obj)
     elif isinstance(obj, Job):
       self.client.delete_job(obj)
 
@@ -106,40 +109,47 @@ class TestSqoopServerBase(SqoopServerProvider):
     for obj in objects:
       self.delete_sqoop_object(obj)
 
-class TestSqoopClientConnections(TestSqoopServerBase):
-  def test_connection(self):
+class TestSqoopClientLinks(TestSqoopServerBase):
+  def test_link(self):
+    raise SkipTest
     try:
       # Create
-      conn = self.create_connection(name='conn1')
-      conn2 = self.client.get_connection(conn.id)
-      assert_true(conn2.id)
-      assert_equal(conn.name, conn2.name)
+      link = self.create_link(name='link1')
+      link2 = self.client.get_link(link.id)
+      assert_true(link2.id)
+      assert_equal(link.name, link2.name)
 
       # Update
-      conn2.name = 'conn-new-1'
-      self.client.update_connection(conn2)
-      conn3 = self.client.get_connection(conn2.id)
-      assert_true(conn3.id)
-      assert_equal(conn2.name, conn3.name)
+      link2.name = 'link-new-1'
+      self.client.update_link(link2)
+      link3 = self.client.get_link(link2.id)
+      assert_true(link3.id)
+      assert_equal(link3.name, link3.name)
     except:
-      self.client.delete_connection(conn3)
+      self.client.delete_link(link3)
 
-  def test_get_connections(self):
+  def test_get_links(self):
+    raise SkipTest
     try:
-      conn = self.create_connection(name='conn2')
-      conns = self.client.get_connections()
-      assert_true(len(conns) > 0)
+      link = self.create_link(name='link2')
+      links = self.client.get_links()
+      assert_true(len(links) > 0)
     finally:
-      self.client.delete_connection(conn)
+      self.client.delete_link(link)
 
 class TestSqoopClientJobs(TestSqoopServerBase):
   def test_job(self):
+    raise SkipTest
     removable = []
+    # Create
+    from_link = self.create_link(name='link3from')
+    to_link = self.create_link(name='link3to')
+
     try:
-      # Create
-      conn = self.create_connection(name='conn3')
-      removable.append(conn)
-      job = self.create_job("IMPORT", "job1", connection_id=conn.id)
+      removable.append(from_link)
+      removable.append(to_link)
+
+      job = self.create_job("job1", from_link_id=from_link.id, to_link_id=to_link.id)
       removable.insert(0, job)
       assert_true(job.id)
 
@@ -155,11 +165,16 @@ class TestSqoopClientJobs(TestSqoopServerBase):
       self.delete_sqoop_objects(removable)
 
   def test_get_jobs(self):
+    raise SkipTest
     removable = []
+    from_link = self.create_link(name='link4from')
+    to_link = self.create_link(name='link4to')
     try:
-      conn = self.create_connection(name='conn4')
-      removable.append(conn)
-      job = self.create_job("IMPORT", "job2", connection_id=conn.id)
+
+      removable.append(from_link)
+      removable.append(to_link)
+
+      job = self.create_job("job2", from_link_id=from_link.id, to_link_id=to_link.id)
       removable.insert(0, job)
       assert_true(job.id)
 

+ 7 - 8
apps/sqoop/src/sqoop/urls.py

@@ -25,15 +25,14 @@ urlpatterns += patterns('sqoop.api',
   url(r'^api/autocomplete/databases/?$', 'autocomplete', name='autocomplete_databases'),
   url(r'^api/autocomplete/databases/(?P<database>.+)/tables/?$', 'autocomplete', name='autocomplete_tables'),
   url(r'^api/autocomplete/databases/(?P<database>.+)/tables/(?P<table>.+)/columns/?$', 'autocomplete', name='autocomplete_fields'),
-  url(r'^api/framework/?$', 'framework', name='framework'),
-  url(r'^api/connectors/?$', 'connectors', name='connectors'),
-  url(r'^api/connectors/resources/?$', 'connectors_resources', name='connectors_resources'),
+  url(r'^api/driver/?$', 'driver', name='driver'),
+  url(r'^api/connectors', 'connectors', name='connectors'),
   url(r'^api/connectors/(?P<connector_id>\d+)/?$', 'connector', name='connector'),
-  url(r'^api/connections/?$', 'connections', name='connections'),
-  url(r'^api/connections/(?P<connection_id>\d+)/?$', 'connection', name='connection'),
-  url(r'^api/connections/(?P<connection_id>\d+)/clone/?$', 'connection_clone', name='connection_clone'),
-  url(r'^api/connections/(?P<connection_id>\d+)/delete/?$', 'connection_delete', name='connection_delete'),
-  url(r'^api/jobs/?$', 'jobs', name='jobs'),
+  url(r'^api/links', 'links', name='links'),
+  url(r'^api/links/(?P<link_id>\d+)/?$', 'link', name='link'),
+  url(r'^api/links/(?P<link_id>\d+)/clone/?$', 'link_clone', name='link_clone'),
+  url(r'^api/links/(?P<link_id>\d+)/delete/?$', 'link_delete', name='link_delete'),
+  url(r'^api/jobs', 'jobs', name='jobs'),
   url(r'^api/jobs/(?P<job_id>\d+)/?$', 'job', name='job'),
   url(r'^api/jobs/(?P<job_id>\d+)/clone/?$', 'job_clone', name='job_clone'),
   url(r'^api/jobs/(?P<job_id>\d+)/delete/?$', 'job_delete', name='job_delete'),

+ 83 - 39
apps/sqoop/static/help/index.html

@@ -11,16 +11,16 @@
 
 
 <h1>Sqoop UI</h1>
-<p>The Sqoop UI enables transfering data from a relational database
-to Hadoop and vice versa. The UI lives uses Apache Sqoop to do this.
-See the <a href="http://sqoop.apache.org/docs/1.99.2/index.html">Sqoop Documentation</a> for more details on Sqoop.</p>
+<p>The Sqoop UI enables transferring data between structured and unstructured data sources. Examples of structured data source are the relational databases such as the MySQL and Postgres, and unstructured, semi-structured data sources include Hbase, HDFS, Cassandra. The UI lives uses Apache Sqoop to do this.
+See the <a href="http://sqoop.apache.org/docs/1.99.4/index.html">Sqoop Documentation</a> for more details on Sqoop.</p>
 
 <p>
-<p>Hue, the <a href="http://gethue.com">open source Big Data UI</a>, has an application that enables transferring data between relational databases and <a href="http://hadoop.apache.org/">Hadoop</a>. This new application is driven by <a href="http://sqoop.apache.org/">Sqoop 2</a> and has several user experience improvements to boot.</p>
+<p>Hue, the <a href="http://gethue.com">open source Big Data UI</a>, has an application that enables transferring data between data sources such as relational databases and <a href="http://hadoop.apache.org/">Hadoop</a>. This new application is driven by <a href="http://sqoop.apache.org/">Sqoop 2</a> and has several user experience improvements to boot.</p>
 <p><iframe frameborder="0" height="495" src="http://player.vimeo.com/video/76063637" width="900"></iframe></p>
-<p>Sqoop is a batch data migration tool for transferring data between traditional databases and Hadoop. The first version of Sqoop is a heavy client that drives and oversees data transfer via MapReduce. In Sqoop 2, the majority of the work was moved to a server that a thin client communicates with. Also, any client can communicate with the Sqoop 2 server over its JSON-REST protocol. Sqoop 2 was chosen instead of its predecessors because of its client-server design.</p>
-<h2>Importing from MySQL to HDFS</h2>
-<p>The following is the canonical import job example sourced from <a href="http://sqoop.apache.org/docs/1.99.2/Sqoop5MinutesDemo.html"><a href="http://sqoop.apache.org/docs/1.99.2/Sqoop5MinutesDemo.html">http://sqoop.apache.org/docs/1.99.2/Sqoop5MinutesDemo.html</a></a>. In Hue, this can be done in 3 easy steps:</p>
+<p>Sqoop is a batch data migration tool for transferring data between traditional databases and Hadoop. The first version of Sqoop is a heavy client that drives and oversees data transfer via MapReduce.</p>
+ <p>In Sqoop 2, the majority of the work was moved to a server that a thin client communicates with. Also, any client can communicate with the Sqoop 2 server over its <a href="http://sqoop.apache.org/docs/1.99.4/RESTAPI.html">JSON-REST API</a>
+ Sqoop 2 was chosen instead of its predecessors because of its client-server design.</p>
+
 <h3>Environment</h3>
 <ul><li>
 <p>CDH 4.4 or <span>Hue 3.0.0</span></p>
@@ -28,22 +28,27 @@ See the <a href="http://sqoop.apache.org/docs/1.99.2/index.html">Sqoop Documenta
 <li>
 <p>MySQL 5.1</p>
 </li>
-</ul><h3>Troubleshooting</h3>
+</ul>
+<h2>Importing FROM MySQL TO HDFS</h2>
+<p>The following is the canonical FROM job example sourced from <a href="http://sqoop.apache.org/docs/1.99.4/Sqoop5MinutesDemo.html"><a href="http://sqoop.apache.org/docs/1.99.4/Sqoop5MinutesDemo.html">Sqoop5MinutesDemo.html</a></a>.</p>
+<p>In Hue, this can be done in 3 easy steps:</p>
 <p>If the new job button is not appearing, Sqoop2 is probably not starting. Make sure the MySql or other DB connectors are in the /usr/lib/sqoop/lib directory of Sqoop2. Make sure you have these properties in the Sqoop2 Server configuration:</p>
 <pre class="code">org.apache.sqoop.repository.schema.immutable=false
 org.apache.sqoop.connector.autoupgrade=true
-org.apache.sqoop.framework.autoupgrade=true 
+org.apache.sqoop.driver.autoupgrade=true 
 </pre>
 
-<h3>1. Create a Connection</h3>
-<p>In the Sqoop app, the connection manager is available from the “New Job” wizard. To get to the new job wizard, click on “New Job”. There may be a list of connections available if a few have been created before. For the purposes of this demo, we’ll go through the process of creating a new connection. Click “Add a new connection” and fill in the blanks with the data below. Then click save to return to the “New Job” wizard!</p>
+<h3>1. Create Links for the From and To</h3>
+<p>In the Sqoop app, the data source link manager is available from the “New Job” wizard. To get to the new job wizard, click on “New Job”. There may be a list of links available if a few have been created before. For the purposes of this demo, we’ll go through the process of creating a new link. Click “Add a new link” and fill in the blanks with the data below.
+ Then click save to return to the “New Job” wizard!</p>
+<p>Add a link for the FROM data source such as the MySQL </p>
 
 <div>
-<pre class="code">Connection Parameter                  Value
+<pre class="code">Link Config Input                     Value
 
-Name                                  mysql-connection-demo 
+Name                                  mysql-link-demo
 
-JDBC Driver Class                     com.mysql.jdbc.Driver 
+JDBC Driver Class                     com.mysql.jdbc.Driver
 
 JDBC Connection String                jdbc:mysql://hue-demo/demo
 
@@ -52,29 +57,68 @@ Username                              demo
 Password                              demo
 </pre>
 </div>
-<p><br/>Connection form values.<br/><br/></p>
+<p>Add a link for the TO data source such as the HDFS </p>
+<div>
+<pre class="code">Link Config Input                     Value
+
+Name                                  hdfs-link-demo 
+
+HDFS URI                              hdfs://hue-demo:8020/demo
+
+</pre>
+</div>
 <h3>2. Create a Job</h3>
-<p>After creating a connection, follow the wizard and fill in the blanks with the information below.</p>
+<p>After creating the FROM and TO links, follow the wizard and fill in the blanks with the information below. We can use the two links created above to associate the From and To for the job.
+
+</p>
+
+<p>Job configuration for the FROM data source</p>
 
 <div>
-<pre class="code">Job Wizard Parameter              Value
+<pre class="code">From Job Config Input                                 Value
+
+Schema name(Optional)
+
+Table name                                             test
 
-Name                              mysql-import-job-demo
+Table SQL statement:(Optional)
 
-Type                              IMPORT
+Table column names:(Optional)
 
-Connection                        mysql-connection-demo
+Partition column name:(Optional)                        id
 
-Table name                        test
+Null value allowed for the partition column:(Optional)
+
+Boundary query:(Optional)
+
+</pre>
+</div>
+
+<p>Job configuration for the TO data source</p>
+
+<div>
+<pre class="code">To Job Config Input                           Value
+
+Output format                                 TEXT_FILE
+
+Compression format(Optional)
+
+Output directory                              /tmp/mysql-import-job-demo
+</pre>
+</div>
+
+
+<p>Job configuration for the Job Execution Drivere</p>
+
+<div>
+<pre class="code">DriverConfig Input                     Value
 
-Storage Type                      HDFS
+Extractors(Optional)                     2
 
-Output format                     TEXT_FILE
+Loaders(Optional)                        2
 
-Output directory                  /tmp/mysql-import-job-demo
 </pre>
 </div>
-<p><br/>Job wizard form values.</p>
 
 <h3>3. Save and Submit the Job</h3>
 <p>At the end of the Job wizard, click “Save and Run”! The job should automagically start after that and the job dashboard will be displayed. As the job is running, a progress bar below the job listing will be dynamically updated. Links to the HDFS output via the File Browser and Map Reduce logs via Job Browser will be available on the left hand side of the job edit page.</p>
@@ -100,18 +144,18 @@ the Hue browser page.</p>
 <ol>
 <li>Click the <strong>New job</strong> button at the top right.</li>
 <li>In the Name field, enter a name.</li>
-<li>Choose the type of job: import or export.
-   The proceeding form fields will change depending on which type is chosen.</li>
-<li>Select a connection, or create one if it does not exist.</li>
+<li>Choose the FROM and TO.
+   The corresponding job  configuration inputs will change depending on FROM or TO chosen.</li>
+<li>Select a link for the FROM or TO data source, or create one if it does not exist.</li>
 <li>Fill in the rest of the fields for the job.
-   For importing, the "Table name", "Storage type", "Output format", and "Output directory" are necessary at a minimum.
-   For exporting, the "Table name" and "Input directory" are necessary at a minimum.</li>
+   For FROM, the "Schema/Table name" and "Input directory" are necessary at a minimum for MySQL and HDFS data sources respectively</li>
+   For MySQL "TO" data source, the "Schema/Table name" is necessary and for HDFS TO data source, the Output directory" are necessary at a minimum.
 <li>Click <strong>save</strong> to finish.</li>
 </ol>
 <h3>Editing a Job</h3>
 <ol>
 <li>In the list of jobs, click on the name of the job.</li>
-<li>Edit the desired form fields in the job.</li>
+<li>Edit the desired configuration fields in the job.</li>
 </ol>
 <h3>Copying a Job</h3>
 <ol>
@@ -135,29 +179,29 @@ as well.</p>
 <li>On the left hand side of the job editor, there should be a panel containing actions.
    Click <strong>Run</strong>.</li>
 </ol>
-<h3>Creating a New Connection</h3>
+<h3>Creating a New Link</h3>
 <ol>
 <li>Click the <strong>New job</strong> button at the top right.</li>
-<li>At the connection field, click the link titled <strong>Add a new connection</strong>.</li>
+<li>At the link field, click the hyperlink titled <strong>Add a new link</strong>.</li>
 <li>Fill in the displayed fields.</li>
 <li>Click <strong>save</strong> to finish.</li>
 </ol>
-<h3>Editing a Connection</h3>
+<h3>Editing a Link</h3>
 <ol>
 <li>Click the <strong>New job</strong> button at the top right.</li>
-<li>At the connection field, select the connection by name that should be edited.</li>
+<li>At the link field, select the link by name that should be edited.</li>
 <li>Click <strong>Edit</strong>.</li>
 <li>Edit the any of the fields.</li>
 <li>Click <strong>save</strong> to finish.</li>
 </ol>
-<h3>Removing a Connection</h3>
+<h3>Removing a Link</h3>
 <ol>
 <li>Click the <strong>New job</strong> button at the top right.</li>
-<li>At the connection field, select the connection by name that should be deleted.</li>
+<li>At the link field, select the link by name that should be deleted.</li>
 <li>Click <strong>Delete</strong>.</li>
 </ol>
-<p>NOTE: If this does not work, it's like because a job is using that connection.
-      Make sure not jobs are using the connection that will be deleted.</p>
+<p>NOTE: If this does not work, it's like because a job is using that link.
+      Make sure not jobs are using the link that will be deleted.</p>
 <h3>Filtering Sqoop Jobs</h3>
 <p>The text field in the top, left corner of the Sqoop Jobs page enables fast filtering
 of sqoop jobs by name.</p>  

+ 20 - 21
apps/sqoop/static/js/sqoop.forms.js → apps/sqoop/static/js/sqoop.configs.js

@@ -34,22 +34,22 @@ function transform_values(model, func_dict) {
   return model;
 }
 
-function to_form(value) {
-  return new forms.FormModel(value);
+function to_config(value) {
+  return new configs.ConfigModel(value);
 }
 
-function to_forms(key, value) {
+function to_configs(key, value) {
   $.each(value, function(index, form_dict) {
-    value[index] = to_form(form_dict);
+    value[index] = to_config(form_dict);
   });
   return value;
 }
 
 function to_input(value) {
   if (value.type.toLowerCase() == 'map') {
-    return new forms.MapInputModel(value);
+    return new configs.MapInputModel(value);
   } else {
-    return new forms.InputModel(value);
+    return new configs.InputModel(value);
   }
 }
 
@@ -61,15 +61,15 @@ function to_inputs(key, value) {
 }
 
 
-var forms = (function($) {
-  var map_form_properties = {
+var configs = (function($) {
+  var map_config_properties = {
     'create': function(options) {
-      return new SqoopForm({modelDict: options.data});
+      return new SqoopConfig({modelDict: options.data});
     },
     'update': function(options) {
       options.target.initialize({modelDict: options.data})
       return options.target;
-    },
+    }
   };
   var map_input_properties = {
     'create': function(options) {
@@ -84,17 +84,17 @@ var forms = (function($) {
     'update': function(options) {
       options.target.initialize({modelDict: options.data})
       return options.target;
-    },
+    }
   };
   var map_properties = {
-    'connector': map_form_properties,
-    'framework': map_form_properties,
-    'con-forms': map_form_properties,
-    'job-forms': map_form_properties,
+    'link-config': map_config_properties,
+    'from-job-config': map_config_properties,
+    'to-job-config': map_config_properties,
+    'driver-config': map_config_properties,
     'inputs': map_input_properties
   };
 
-  var FormModel = koify.Model.extend({
+  var ConfigModel = koify.Model.extend({
     'id': -1,
     'inputs': [],
     'name': null,
@@ -152,9 +152,8 @@ var forms = (function($) {
     }
   });
 
-  // Form is reserved word
-  var SqoopForm = koify.MinimalNode.extend({
-    'model_class': FormModel,
+  var SqoopConfig = koify.MinimalNode.extend({
+    'model_class': ConfigModel,
     'map': function() {
       var self = this;
       var mapping_options = $.extend(true, {
@@ -224,10 +223,10 @@ var forms = (function($) {
   });
 
   return {
-    'FormModel': FormModel,
+    'ConfigModel': ConfigModel,
     'InputModel': InputModel,
     'MapInputModel': MapInputModel,
-    'Form': SqoopForm,
+    'Config': SqoopConfig,
     'Input': SqoopInput,
     'MapInput': SqoopMapInput,
     'MapProperties': map_properties

+ 12 - 12
apps/sqoop/static/js/sqoop.connectors.js

@@ -20,26 +20,26 @@ var connectors = (function($) {
     'id': -1,
     'name': null,
     'class': null,
-    'job_forms': {
-      'IMPORT': [],
-      'EXPORT': []
+    'job_config': {
+      'FROM': [],
+      'TO': []
     },
-    'con_forms': [],
+    'link_config': [],
     'version': null,
-    'resources': {},
+    'config_resources': {},
     'initialize': function(attrs) {
       var self = this;
       var _attrs = $.extend(true, {}, attrs);
       _attrs = transform_keys(_attrs, {
-        'job-forms': 'job_forms',
-        'con-forms': 'con_forms'
+        'link_config': 'link_config',
+        'job_config': 'job_config'
       });
       _attrs = transform_values(_attrs, {
-        'con_forms': to_forms,
-        'job_forms': function(key, value) {
+        'link_config': to_configs,
+        'job_config': function(key, value) {
           transform_values(value, {
-            'IMPORT': to_forms,
-            'EXPORT': to_forms
+            'FROM': to_configs,
+            'TO': to_configs
           });
           return value;
         }
@@ -62,7 +62,7 @@ var connectors = (function($) {
       var self = this;
       var mapping_options = $.extend(true, {
         'ignore': ['parent', 'initialize']
-      }, forms.MapProperties);
+      }, configs.MapProperties);
       if ('__ko_mapping__' in self) {
         ko.mapping.fromJS(self.model, mapping_options, self);
       } else {

+ 13 - 25
apps/sqoop/static/js/sqoop.framework.js → apps/sqoop/static/js/sqoop.driver.js

@@ -16,41 +16,29 @@
 
 
 
-var framework = (function($) {
-  var FrameworkModel = koify.Model.extend({
+var driver = (function($) {
+  var DriverModel = koify.Model.extend({
     'id': 1,
-    'job_forms': {
-      'IMPORT': [],
-      'EXPORT': []
-    },
-    'con_forms': [],
-    'resources': {},
+    'job_config': [],
+    'config_resources': {},
     'initialize': function(attrs) {
       var self = this;
       var _attrs = $.extend(true, {}, attrs);
       _attrs = transform_keys(_attrs, {
-        'job-forms': 'job_forms',
-        'con-forms': 'con_forms'
+        'job_config': 'job_config'
       });
       _attrs = transform_values(_attrs, {
-        'con_forms': to_forms,
-        'job_forms': function(key, value) {
-          transform_values(value, {
-            'IMPORT': to_forms,
-            'EXPORT': to_forms
-          });
-          return value;
-        }
+        'job_config': to_configs
       });
       return _attrs;
     }
   });
 
-  var Framework = koify.Node.extend({
-    'identifier': 'framework',
+  var Driver = koify.Node.extend({
+    'identifier': 'driver',
     'persists': false,
-    'model_class': FrameworkModel,
-    'base_url': '/sqoop/api/framework/',
+    'model_class': DriverModel,
+    'base_url': '/sqoop/api/driver/',
     'initialize': function() {
       var self = this;
       self.parent.initialize.apply(self, arguments);
@@ -60,7 +48,7 @@ var framework = (function($) {
       var self = this;
       var mapping_options = $.extend(true, {
         'ignore': ['parent', 'initialize']
-      }, forms.MapProperties);
+      }, configs.MapProperties);
       if ('__ko_mapping__' in self) {
         ko.mapping.fromJS(self.model, mapping_options, self);
       } else {
@@ -71,7 +59,7 @@ var framework = (function($) {
   });
 
   return {
-    'FrameworkModel': FrameworkModel,
-    'Framework': Framework
+    'DriverModel': DriverModel,
+    'Driver': Driver
   }
 })($);

+ 36 - 31
apps/sqoop/static/js/sqoop.jobs.js

@@ -22,23 +22,25 @@ var jobs = (function($) {
   var JobModel = koify.Model.extend({
     'id': -1,
     'name': null,
-    'type': 'IMPORT',
-    'connector_id': 0,
-    'connection_id': 0,
-    'connector': [],
-    'framework': [],
+    'from_connector_id': 0,
+    'from_link_id': 0,
+    'to_connector_id': 0,
+    'to_link_id': 0,
+    'from_config_values': [],
+    'to_config_values': [],
+    'driver_config_values': [],
     'creation_date': null,
     'creation_user': null,
     'update_date': null,
     'update_user': null,
-    'setImport': function(){
-      this.type("IMPORT");
+    'setFrom': function(){
+      this.type("FROM");
       // Huge hack for now
       $('a').filter(function(index) { return $(this).text() === "Step 2: To"; }).text("Step 2: From");
       $('a').filter(function(index) { return $(this).text() === "Step 3: From"; }).text("Step 3: To");
     },
-    'setExport': function(){
-      this.type("EXPORT");
+    'setTo': function(){
+      this.type("TO");
       $('a').filter(function(index) { return $(this).text() === "Step 2: From"; }).text("Step 2: To");
       $('a').filter(function(index) { return $(this).text() === "Step 3: To"; }).text("Step 3: From");
     },
@@ -46,12 +48,15 @@ var jobs = (function($) {
       var self = this;
       var _attrs = $.extend(true, {}, attrs);
       _attrs = transform_keys(_attrs, {
-        'connector-id': 'connector_id',
-        'connection-id': 'connection_id'
+        'from-connector-id': 'from_connector_id',
+        'to-connector-id': 'to_connector_id',
+        'from-link-id': 'from_link_id',
+        'to-link-id': 'to_link_id'
       });
       _attrs = transform_values(_attrs, {
-        'connector': to_forms,
-        'framework': to_forms
+        'from-config-values': to_configs,
+        'to-config-values': to_configs,
+        'driver-config-values': to_configs
       });
       return _attrs;
     }
@@ -67,14 +72,14 @@ var jobs = (function($) {
       self.parent.initialize.apply(self, arguments);
       self.createdFormatted = ko.computed(function() {
         if (self.creation_date()) {
-          return moment(self.creation_date()).format('MM/DD/YYYY hh:mm A');
+          return moment(self.creation_date()).configat('MM/DD/YYYY hh:mm A');
         } else {
           return 0;
         }
       });
       self.updatedFormatted = ko.computed(function() {
         if (self.update_date()) {
-          return moment(self.update_date()).format('MM/DD/YYYY hh:mm A');
+          return moment(self.update_date()).configat('MM/DD/YYYY hh:mm A');
         } else {
           return 0;
         }
@@ -115,9 +120,9 @@ var jobs = (function($) {
       });
       self.outputDirectoryFilebrowserURL = ko.computed(function() {
         var output_directory = null;
-        $.each(self.framework(), function(index, form) {
-          if (form.name() == 'output') {
-            $.each(form.inputs(), function(index, input) {
+        $.each(self.to_config_values(), function(index, config) {
+          if (config.name() == 'output') {
+            $.each(config.inputs(), function(index, input) {
               if (input.name() == 'output.outputDirectory') {
                 output_directory = input.value();
               }
@@ -128,9 +133,9 @@ var jobs = (function($) {
       });
       self.inputDirectoryFilebrowserURL = ko.computed(function() {
         var input_directory = null;
-        $.each(self.framework(), function(index, form) {
-          if (form.name() == 'input') {
-            $.each(form.inputs(), function(index, input) {
+        $.each(self.from_config_values(), function(index, config) {
+          if (config.name() == 'input') {
+            $.each(config.inputs(), function(index, input) {
               if (input.name() == 'input.inputDirectory') {
                 input_directory = input.value();
               }
@@ -141,11 +146,11 @@ var jobs = (function($) {
       });
       self.storageType = ko.computed(function() {
         var storage_type = null;
-        $.each(self.framework(), function(index, form) {
-          if (form.name() == 'input') {
+        $.each(self.from_config_values(), function(index, config) {
+          if (config.name() == 'input') {
             storage_type = 'HDFS'; // Hardcoded for now
-          } else if (form.name() == 'output') {
-            $.each(form.inputs(), function(index, input) {
+          } else if (config.name() == 'output') {
+            $.each(config.inputs(), function(index, input) {
               if (input.name() == 'output.storageType') {
                 storage_type = input.value();
               }
@@ -156,9 +161,9 @@ var jobs = (function($) {
       });
       self.table = ko.computed(function() {
         var table = null;
-        $.each(self.connector(), function(index, form) {
-          if (form.name() == 'table') {
-            $.each(form.inputs(), function(index, input) {
+        $.each(self.from_config_values(), function(index, config) {
+          if (config.name() == 'table') {
+            $.each(config.inputs(), function(index, input) {
               if (input.name() == 'table.tableName') {
                 table = input.value();
               }
@@ -174,7 +179,7 @@ var jobs = (function($) {
       var self = this;
       var mapping_options = $.extend(true, {
         'ignore': ['parent', 'initialize']
-      }, forms.MapProperties);
+      }, configs.MapProperties);
       if ('__ko_mapping__' in self) {
         ko.mapping.fromJS(self.model, mapping_options, self);
       } else {
@@ -205,7 +210,7 @@ var jobs = (function($) {
     },
     'stop': function(options) {
       var self = this;
-      $(document).trigger('start.job', [options, self]);
+      $(document).trigger('stop.job', [options, self]);
       var options = $.extend({
         type: 'POST',
         success: function(data) {
@@ -281,6 +286,6 @@ var jobs = (function($) {
     'Job': Job,
     'fetchJobs': fetch_jobs,
     'putJob': put_job,
-    'getJob': get_job,
+    'getJob': get_job
   }
 })($);

+ 103 - 113
apps/sqoop/static/js/sqoop.js

@@ -55,18 +55,16 @@ function showSubsection(mainSection, section, subSection) {
 
 
 //// Constructors
-function create_connection(attrs, options) {
+function create_link(attrs, options) {
   var options = options || {};
   options.modelDict = attrs || {};
-  var node = new connections.Connection(options);
-  // Need a copy of the forms so that when editing
-  // we don't re-use forms.
-  $.each(viewModel.connector().con_forms(), function(index, form) {
-    node.connector.push($.extend(true, {}, form));
-  });
-  $.each(viewModel.framework().con_forms(), function(index, form) {
-    node.framework.push($.extend(true, {}, form));
+  var node = new links.Link(options);
+  // Need a copy of the configs so that when editing
+  // we don't re-use configs.
+  $.each(viewModel.link().link_config_values(), function(index, config) {
+    node.connector.push($.extend(true, {}, config));
   });
+
   return node;
 }
 
@@ -87,12 +85,12 @@ var viewModel = new (function() {
   self.sqoop_errors = ko.observableArray([]);
   self.errors = ko.observable({});
   self.warnings = ko.observable({});
-  self.framework = ko.observable();
+  self.driver = ko.observable();
   self.connectors = ko.observableArray();
-  self.connections = ko.observableArray();
+  self.links = ko.observableArray();
   self.jobs = ko.observableArray();
-  self.connection = ko.observable();
-  self.editConnection = ko.observable();
+  self.link = ko.observable();
+  self.editLink = ko.observable();
   self.modal = {
     'name': ko.observable()
   };
@@ -110,12 +108,12 @@ var viewModel = new (function() {
 
   // Must always have a value.
   self.connector = ko.computed(function() {
-    // Fall back to first connector so that a connector is selected when we are creating a connection.
-    if (!self.connection()) {
+    // Fall back to first connector so that a connector is selected when we are creating a link.
+    if (!self.link()) {
       return self.connectors()[0];
     }
     var connectorArr = ko.utils.arrayFilter(self.connectors(), function (connector) {
-      return connector.id() == self.connection().connector_id();
+      return connector.id() == self.link().connector_id();
     });
     return (connectorArr.length > 0) ? connectorArr[0] : self.connectors()[0];
   });
@@ -124,9 +122,9 @@ var viewModel = new (function() {
       return job.persisted();
     });
   });
-  self.persistedConnections = ko.computed(function() {
-    return ko.utils.arrayFilter(self.connections(), function (connection) {
-      return connection.persisted();
+  self.persistedLinks = ko.computed(function() {
+    return ko.utils.arrayFilter(self.links(), function (link) {
+      return link.persisted();
     });
   });
   self.filteredJobs = ko.computed(function() {
@@ -139,11 +137,11 @@ var viewModel = new (function() {
       }
     });
   });
-  self.filteredConnections = ko.computed(function() {
+  self.filteredLinks = ko.computed(function() {
     var filter = self.filter().toLowerCase();
-    return ko.utils.arrayFilter(self.persistedConnections(), function (connection) {
-      if (connection.name()) {
-        return connection.name().toLowerCase().indexOf(filter) > -1 || connection.type().toLowerCase().indexOf(filter) > -1;
+    return ko.utils.arrayFilter(self.persistedLinks(), function (link) {
+      if (link.name()) {
+        return link.name().toLowerCase().indexOf(filter) > -1 || link.type().toLowerCase().indexOf(filter) > -1;
       } else {
         return false;
       }
@@ -165,52 +163,45 @@ var viewModel = new (function() {
   });
 
 
-  // Update forms for connectors, jobs, and connections.
-  // In sqoop, the connector and framework provides
-  // attributes that need to be filled in for connections
-  // and jobs. The framework and connector will provide
-  // different forms for IMPORT and EXPORT jobs.
-  self.framework.subscribe(function(value) {
-    // We assume that the framework components
-    // are not going to change so we do not update connection
-    // and job objects unless they lack forms.
+  // The driver and connector provide configurations for job
+  self.driver.subscribe(function(value) {
+    // We assume that the driver components
+    // are not going to change so w do not update job objects unless they lack configs.
     if (value) {
-      if (self.editConnection() && self.editConnection().framework().length == 0) {
-        self.editConnection().framework(value.con_forms());
-      }
-      if (self.job() && self.job().framework().length == 0) {
-        var type = self.job().type().toUpperCase();
-        self.job().framework(value.job_forms[type]());
+      if (self.job() && self.job().driver_config_values().length == 0) {
+        self.job().driver_config_values(value.job_config());
       }
     }
   });
 
   self.connector.subscribe(function(value) {
     // We assume that the connectors component
-    // are not going to change so we do not update connection
-    // and job objects unless they lack forms.
+    // are not going to change so we do not update link
+    // and job objects unless they lack configs.
     if (value) {
-      if (self.editConnection() && self.editConnection().connector().length == 0) {
-        self.editConnection().connector(value.con_forms());
+      if (self.editLink() && self.editLink().link_config_values().length == 0) {
+        self.editLink().link_config_values(value.link_config());
       }
-      if (self.job() && self.job().connector().length == 0) {
-        var type = self.job().type().toUpperCase();
-        self.job().connector(value.job_forms[type]());
+      if (self.job() && self.job().from_config_values().length == 0) {
+        self.job().from_config_values(value.job_config['FROM']());
+      }
+       if (self.job() && self.job().to_config_values().length == 0) {
+        self.job().to_config_values(value.job_config['TO']());
       }
     }
   });
 
-  // Forms are swapped between IMPORT and EXPORT types.
+  // Forms are swapped between FROM and TO types.
   // Use of "beforeChange" subscription event to
   // remove subscriptions and help with swapping.
   var job_type_subscriptions = [];
-  var old_connector_forms = {
-    'IMPORT': null,
-    'EXPORT': null
+  var old_connector_configs = {
+    'FROM': null,
+    'TO': null
   };
-  var old_framework_forms = {
-    'IMPORT': null,
-    'EXPORT': null
+  var old_driver_configs = {
+    'FROM': null,
+    'TO': null
   };
   self.job.subscribe(function(old_job) {
     if (job_type_subscriptions) {
@@ -221,43 +212,42 @@ var viewModel = new (function() {
   }, self, "beforeChange");
   self.job.subscribe(function(job) {
     if (job) {
-      var type = job.type().toUpperCase();
 
-      if (self.connector() && job.connector().length == 0) {
-        job.connector(self.connector().job_forms[type]());
+      if (self.from_config_values() && job.from_config_values().length == 0) {
+        job.from_config_values(self.from_config_values());
       }
 
-      if (self.framework() && job.framework().length == 0) {
-        job.framework(self.framework().job_forms[type]());
+      if (self.to_config_values() && job.to_config_values().length == 0) {
+        job.to_config_values(self.to_config_values());
+      }
+      if (self.driver_config_values() && job.driver_config_values().length == 0) {
+        job.driver_config_values(self.driver_config_values());
       }
 
-      job_type_subscriptions.push(job.type.subscribe(function(new_type) {
-        var connector = old_connector_forms[new_type] || self.connector().job_forms[new_type]();
-        var framework = old_framework_forms[new_type] || self.framework().job_forms[new_type]();
-        old_connector_forms[new_type] = null;
-        old_framework_forms[new_type] = null;
+      /*job_type_subscriptions.push(job.type.subscribe(function(new_type) {
+        var connector = old_connector_configs[new_type] || self.connector().job_configs[new_type]();
+        var driver = old_driver_configs[new_type] || self.driver().job_configs[new_type]();
+        old_connector_configs[new_type] = null;
+        old_driver_configs[new_type] = null;
         job.connector(connector);
-        job.framework(framework);
+        job.driver(driver);
       }));
 
       job_type_subscriptions.push(job.type.subscribe(function(old_type) {
         if (job.connector().length > 0) {
-          old_connector_forms[old_type] = job.connector();
+          old_connector_configs[old_type] = job.connector();
         }
-        if (job.framework().length > 0) {
-          old_framework_forms[old_type] = job.framework();
+        if (job.driver_config_values().length > 0) {
+          old_driver_configs[old_type] = job.driver();
         }
-      }, self, "beforeChange"));
+      }, self, "beforeChange"));*/
     }
   });
 
-  self.editConnection.subscribe(function() {
-    if (self.editConnection()) {
-      if (self.connector() && self.editConnection().connector().length == 0) {
-        self.editConnection().connector(self.connector().con_forms());
-      }
-      if (self.framework() && !self.editConnection().framework().length == 0) {
-        self.editConnection().framework(self.framework().con_forms());
+  self.editLink.subscribe(function() {
+    if (self.editLink()) {
+      if (self.link_config_values() && self.editLink().link_config_values().length == 0) {
+        self.editLink().link_config_values(self.link_config_values());
       }
     }
   });
@@ -267,39 +257,39 @@ var viewModel = new (function() {
     self.warnings({});
   });
 
-  self.newConnection = function() {
+  self.newLink = function() {
     var self = this;
-    if (!self.connection() || self.connection().persisted()) {
-      var conn = create_connection();
-      self.editConnection(conn);
+    if (!self.link() || self.link().persisted()) {
+      var conn = create_link();
+      self.editLink(conn);
     }
   };
 
-  self.saveConnection = function() {
-    var connection = self.editConnection();
-    if (connection) {
-      connection.connector_id(self.connector().id());
-      connection.save();
+  self.saveLink = function() {
+    var link = self.editLink();
+    if (link) {
+      link.connector_id(self.connector().id());
+      link.save();
     }
   };
 
-  self.getConnectionById = function(id) {
-    var connection = null;
-    $.each(self.connections(), function(index, conn) {
+  self.getLinkById = function(id) {
+    var link = null;
+    $.each(self.links(), function(index, conn) {
       if (conn.id() == id) {
-        connection = conn;
+        link = conn;
       }
     });
-    return connection;
+    return link;
   };
 
-  self.chooseConnectionById = function(id) {
+  self.chooseLinkById = function(id) {
     var self = this;
-    self.editConnection(self.getConnectionById(id) || self.editConnection());
+    self.editLink(self.getLinkById(id) || self.editLink());
   };
 
-  self.deselectAllConnections = function() {
-    $.each(self.connections(), function(index, value) {
+  self.deselectAllLinks = function() {
+    $.each(self.links(), function(index, value) {
       value.selected(false);
     });
   };
@@ -318,12 +308,12 @@ var viewModel = new (function() {
   self.saveJob = function() {
     var job = self.job();
     if (job) {
-      if (!self.connection()) {
-        $(document).trigger('connection_missing.job', [self, null, {}]);
+      if (!self.link()) {
+        $(document).trigger('link_missing.job', [self, null, {}]);
         return;
       }
       job.connector_id((self.connector()) ? self.connector().id() : null);
-      job.connection_id((self.connection()) ? self.connection().id() : null);
+      job.link_id((self.link()) ? self.link().id() : null);
       job.save();
     }
   };
@@ -371,15 +361,15 @@ var viewModel = new (function() {
     return self[component]().resources[name + '.help'];
   };
 
-  self.getDatabaseByConnectionId = function(id) {
+  self.getDatabaseByLinkId = function(id) {
     var self = this;
-    var connection = self.getConnectionById(id);
-    if (connection) {
-      var connection_string = connection.connectionString();
-      if (connection_string) {
-        var connection_string_parts = connection.connectionString().split(':');
-        if (connection_string_parts.length > 2) {
-          return connection_string_parts[1].toUpperCase();
+    var link = self.getLinkById(id);
+    if (link) {
+      var link_string = link.linkString();
+      if (link_string) {
+        var link_string_parts = link.linkString().split(':');
+        if (link_string_parts.length > 2) {
+          return link_string_parts[1].toUpperCase();
         }
       }
     }
@@ -398,9 +388,9 @@ var viewModel = new (function() {
     self.showModal(name);
   };
 
-  self.showDeleteConnectionModal = function() {
+  self.showDeleteLinkModal = function() {
     var self = this;
-    var name = 'delete-connection-modal';
+    var name = 'delete-link-modal';
     self.showModal(name);
   }
 
@@ -426,18 +416,18 @@ var viewModel = new (function() {
 })();
 
 //// Event handling
-function set_framework(e, framework, options) {
-  viewModel.framework(framework);
+function set_driver(e, driver, options) {
+  viewModel.driver(driver);
 }
 
 function set_connectors(e, connectors, options) {
   viewModel.connectors(connectors);
 }
 
-function set_connections(e, connections, options) {
-  viewModel.connections(connections);
-  if (viewModel.connections().length > 0) {
-    viewModel.connection(viewModel.connections()[0]);
+function set_links(e, links, options) {
+  viewModel.links(links);
+  if (viewModel.links().length > 0) {
+    viewModel.link(viewModel.links()[0]);
   }
 }
 
@@ -455,8 +445,8 @@ function update_job_submissions(e, submissions, options) {
   });
 }
 
-$(document).on('loaded.framework', set_framework);
+$(document).on('loaded.driver', set_driver);
 $(document).on('loaded.connectors', set_connectors);
-$(document).on('loaded.connections', set_connections);
+$(document).on('loaded.links', set_links);
 $(document).on('loaded.jobs', set_jobs);
 $(document).on('loaded.submissions', update_job_submissions);

+ 46 - 48
apps/sqoop/static/js/sqoop.connections.js → apps/sqoop/static/js/sqoop.links.js

@@ -15,17 +15,16 @@
 // limitations under the License.
 
 
-var connections = (function($) {
-  var ConnectionModel = koify.Model.extend({
+var links = (function($) {
+  var LinkModel = koify.Model.extend({
     'id': -1,
     'name': null,
-    'connector': [],
+    'link_config_values': [],
     'connector_id': 0,
     'creation_date': null,
     'creation_user': null,
     'update_date': null,
     'update_user': null,
-    'framework': [],
     'initialize': function(attrs) {
       var self = this;
       var _attrs = $.extend(true, {}, attrs);
@@ -33,18 +32,17 @@ var connections = (function($) {
         'connector-id': 'connector_id'
       });
       _attrs = transform_values(_attrs, {
-        'connector': to_forms,
-        'framework': to_forms
+        'link_config_values': to_configs
       });
       return _attrs;
     }
   });
 
-  var Connection = koify.Node.extend({
-    'identifier': 'connection',
+  var Link = koify.Node.extend({
+    'identifier': 'linkConfig',
     'persists': true,
-    'model_class': ConnectionModel,
-    'base_url': '/sqoop/api/connections/',
+    'model_class': LinkModel,
+    'base_url': '/sqoop/api/links/',
     'initialize': function(options) {
       var self = this;
       self.parent.initialize.apply(self, arguments);
@@ -53,24 +51,24 @@ var connections = (function($) {
         return self.id() > -1;
       });
       self.connectionString = ko.computed(function() {
-        var connection_string = null;
-        $.each(self.connector(), function(index, form) {
-          if (form.name() == 'connection') {
-            $.each(form.inputs(), function(index, input) {
-              if (input.name() == 'connection.connectionString') {
-                connection_string = input.value();
+        var link_string = null;
+        $.each(self.link_config_values(), function(index, config) {
+          if (config.name() == 'linkConfig') {
+            $.each(config.inputs(), function(index, input) {
+              if (input.name() == 'linkConfig.connectionString') {
+                link_string = input.value();
               }
             });
           }
         });
-        return connection_string;
+        return link_string;
       });
       self.jdbcDriver = ko.computed(function() {
         var jdbc_driver = null;
-        $.each(self.connector(), function(index, form) {
-          if (form.name() == 'connection') {
-            $.each(form.inputs(), function(index, input) {
-              if (input.name() == 'connection.jdbcDriver') {
+        $.each(self.link_config_values(), function(index, config) {
+          if (config.name() == 'linkConfig') {
+            $.each(config.inputs(), function(index, input) {
+              if (input.name() == 'linkConfig.jdbcDriver') {
                 jdbc_driver = input.value();
               }
             });
@@ -157,10 +155,10 @@ var connections = (function($) {
       });
       self.username = ko.computed(function() {
         var username = null;
-        $.each(self.connector(), function(index, form) {
-          if (form.name() == 'connection') {
-            $.each(form.inputs(), function(index, input) {
-              if (input.name() == 'connection.username') {
+        $.each(self.link_config_values(), function(index, config) {
+          if (config.name() == 'linkConfig') {
+            $.each(config.inputs(), function(index, input) {
+              if (input.name() == 'linkConfig.username') {
                 username = input.value();
               }
             });
@@ -170,10 +168,10 @@ var connections = (function($) {
       });
       self.password = ko.computed(function() {
         var password = null;
-        $.each(self.connector(), function(index, form) {
-          if (form.name() == 'connection') {
-            $.each(form.inputs(), function(index, input) {
-              if (input.name() == 'connection.password') {
+        $.each(self.link_config_values(), function(index, config) {
+          if (config.name() == 'linkConfig') {
+            $.each(config.inputs(), function(index, input) {
+              if (input.name() == 'linkConfig.password') {
                 password = input.value();
               }
             });
@@ -196,34 +194,34 @@ var connections = (function($) {
       });
     },
     'map': function() {
-      var self = this;
-      var mapping_options = $.extend(true, {
-        'ignore': ['parent', 'initialize']
-      }, forms.MapProperties);
-      if ('__ko_mapping__' in self) {
-        ko.mapping.fromJS(self.model, mapping_options, self);
-      } else {
-        var mapped = ko.mapping.fromJS(self.model, mapping_options);
-        $.extend(self, mapped);
-      }
-    },
+        var self = this;
+        var mapping_options = $.extend(true, {
+            'ignore': ['parent', 'initialize']
+        }, configs.MapProperties);
+        if ('__ko_mapping__' in self) {
+            ko.mapping.fromJS(self.model, mapping_options, self);
+        } else {
+            var mapped = ko.mapping.fromJS(self.model, mapping_options);
+            $.extend(self, mapped);
+        }
+    }
   });
 
-  function fetch_connections(options) {
-    $(document).trigger('load.connections', [options]);
+  function fetch_links(options) {
+    $(document).trigger('load.links', [options]);
     var request = $.extend({
-      url: '/sqoop/api/connections/',
+      url: '/sqoop/api/links/',
       dataType: 'json',
       type: 'GET',
-      success: fetcher_success('connections', Connection, options),
-      error: fetcher_error('connections', Connection, options)
+      success: fetcher_success('links', Link, options),
+      error: fetcher_error('links', Link, options)
     }, options || {});
     $.ajax(request);
   }
 
   return {
-    'ConnectionModel': ConnectionModel,
-    'Connection': Connection,
-    'fetchConnections': fetch_connections
+    'LinkModel': LinkModel,
+    'Link': Link,
+    'fetchLinks': fetch_links
   }
 })($);

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است